سؤال

قادم من مجتمع OCAML ، أحاول أن أتعلم القليل من Haskell. يسير الانتقال بشكل جيد ولكن أنا مرتبك بعض الشيء مع تصحيح الأخطاء. اعتدت وضع (الكثير من) "printf" في رمز OCAML الخاص بي ، لتفقد بعض القيم الوسيطة ، أو كعلم لمعرفة أين فشل الحساب بالضبط.

لأن printf هو IO الإجراء ، هل يجب أن أرفع كل رمز Haskell داخل IO موناد لتكون قادرا على هذا النوع من التصحيح؟ أم أن هناك طريقة أفضل للقيام بذلك (لا أريد فعل ذلك باليد إذا كان يمكن تجنبها)

أجد أيضا أثر وظيفة :http://www.haskell.org/haskellwiki/Debugging#printf_and_friendsما يبدو بالضبط ما أريد ، لكنني لا أفهم أنه نوع: لا يوجد IO في أى مكان! هل يمكن لأحد أن يشرح لي سلوك وظيفة التتبع؟

هل كانت مفيدة؟

المحلول

trace هو الأسهل في استخدام طريقة تصحيح الأخطاء. انها ليست في IO بالضبط للسبب الذي أشار إليه: لا حاجة لرفع الكود الخاص بك في IO موناد. تم تنفيذه مثل هذا

trace :: String -> a -> a
trace string expr = unsafePerformIO $ do
    putTraceMsg string
    return expr

لذلك هناك IO وراء الكواليس ولكن unsafePerformIO يستخدم للهروب منه. هذه وظيفة من المحتمل أن تكسر الشفافية المرجعية التي يمكنك تخمين النظر في نوعها IO a -> a وكذلك اسمها.

نصائح أخرى

trace هو ببساطة نجس. نقطة IO موناد هو الحفاظ على الطهارة (لا يوجد دون أن يلاحظه أحد من قبل نظام النوع) وتحديد ترتيب تنفيذ البيانات ، والتي لن يتم تعريفها عملياً من خلال التقييم كسول.

ومع ذلك ، على عاتقها ، يمكنك مع ذلك اختراق بعض IO a -> a, ، أي أداء نجس. هذا اختراق وبالطبع "يعاني" من التقييم الكسول ، ولكن هذا ما يفعله تتبع ببساطة من أجل تصحيح الأخطاء.

ومع ذلك ، ربما يجب أن تذهب طرقًا أخرى لتصحيح الأخطاء:

  1. تقليل الحاجة إلى تصحيح القيم الوسيطة

    • اكتب وظائف صغيرة ، قابلة لإعادة الاستخدام ، واضحة ، واضحة ، واضحة.
    • الجمع بين القطع الصحيحة إلى قطع صحيحة أكبر.
    • يكتب الاختبارات أو جرب القطع بشكل تفاعلي.
  2. استخدم نقاط التوقف وما إلى ذلك (تصحيح الأخطاء المستندة إلى المترجم)

  3. استخدام الموناد العام. إذا كان الكود الخاص بك أحاديًا ، فاكتبه بشكل مستقل عن موناد ملموس. يستخدم type M a = ... بدلا من عادي IO .... يمكنك بعد ذلك الجمع بسهولة بين الموناد من خلال المحولات ووضع موناد تصحيح الأخطاء فوقه. حتى لو اختفت الحاجة إلى الموناد ، يمكنك فقط الإدراج Identity a للقيم النقية.

لما يستحق ، هناك بالفعل نوعان من "تصحيح الأخطاء" في القضية هنا:

  • تسجيل القيم الوسيطة ، مثل القيمة التي يتمتع بها التعبير الفرعي معين على كل مكالمة في وظيفة متكررة
  • فحص سلوك وقت التشغيل لتقييم التعبير

بلغة ضرورية صارمة هذه عادة ما تتزامن. في هاسكل ، لا يفعلون ذلك في كثير من الأحيان:

  • يمكن أن يغير تسجيل القيم الوسيطة سلوك وقت التشغيل ، مثل فرض تقييم المصطلحات التي يمكن التخلص منها.
  • يمكن أن تختلف العملية الفعلية للحساب بشكل كبير عن البنية الظاهرة للتعبير بسبب الكسل والاعتدال الفرعي المشترك.

إذا كنت ترغب فقط في الاحتفاظ بسجل للقيم المتوسطة ، فهناك العديد من الطرق للقيام بذلك-على سبيل المثال ، بدلاً من رفع كل شيء إلى IO, ، بسيط Writer سوف يكون Monad كافياً ، وهذا يعادل جعل الوظائف تعيد 2-tuple من النتيجة الفعلية وقيمة التراكم (نوع من القائمة ، عادة).

كما أنه ليس من الضروري عادة وضع كل شىء في الموناد ، فقط الوظائف التي تحتاج إلى الكتابة إلى قيمة "السجل"-على سبيل المثال ، يمكنك وضع فقط التعبيرات الفرعية التي قد تحتاج إلى التسجيل ، وترك المنطق الرئيسي نقيًا ، ثم إعادة تجميع الحساب العام عن طريق الجمع وظائف وحسابات التسجيل بالطريقة المعتادة مع fmapق وما. لا تنسى Writer هو نوع من العذر المؤسق لموناد: دون أي وسيلة للقراءة من السجل ، يكتب إليه فقط ، كل حساب مستقل منطقيًا عن سياقه ، مما يجعل من السهل التوفيق بين الأشياء.

ولكن في بعض الحالات ، يكون ذلك مبالٍ-مقابل العديد من الوظائف الخالصة ، فقط نقل التعبيرات الفرعية إلى toplevel وتجربة الأشياء في الاستبدال تعمل بشكل جيد.

إذا كنت ترغب بالفعل في فحص سلوك وقت التشغيل في الكود الخالص ، على الرغم من ذلك-على سبيل المثال ، لمعرفة سبب اختلاف التعبير الفرعي-هناك بشكل عام لا توجد طريقة للقيام بذلك من رمز نقي آخر-في الحقيقة ، هذا هو في الأساس تعريف من الطهارة. لذلك في هذه الحالة ، ليس لديك خيار سوى استخدام الأدوات الموجودة "خارج" اللغة النقية: إما وظائف غير نجسة مثل unsafePerformPrintfDebugging-errr ، أعني trace-أو بيئة وقت التشغيل المعدلة ، مثل DESPUGGER GHCI.

trace يميل أيضًا إلى الإفراط في تقييم حجتها للطباعة ، وفقدان الكثير من فوائد الكسل في هذه العملية.

إذا تمكنت من الانتظار حتى يتم الانتهاء من البرنامج قبل دراسة الإخراج ، ثم تكديس أ الكاتب موناد هو النهج الكلاسيكي لتنفيذ مسجل. انا استعمل هذا هنا لإرجاع مجموعة النتيجة من رمز HDBC الناقص.

حسنًا ، نظرًا لأن Haskell كله مبني حول مبدأ التقييم الكسول (بحيث يكون ترتيب الحسابات في الواقع غير محدد) ، فإن استخدام PrintF ليس له معنى كبير فيه.

إذا كان REPLESS REPLESS ، لا يكفي في الحقيقة ، لا يكفي لتصحيح الأخطاء الخاصة بك ، فإن لف كل شيء في IO هو الخيار الوحيد (ولكنه ليس الطريقة الصحيحة لبرمجة Haskell).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top