سؤال

أحاول استخدام Monaderror مع Parsec. لقد توصلت إلى مقتطف الكود التالي:

f5 = do
    char 'a'
    throwError "SomeError"

f6 = f5 `catchError` (\e -> unexpected $ "Got the error: " ++ e)

ret = runErrorT (runParserT f6 () "stdin" "a")

لكن، ret هو Left "SomeError", ، يبدو أن catcherror ليس له أي تأثير. ما هي الطريقة الصحيحة لاستخدام monaderror هنا؟

أفضل استخدام Monaderror على معالجة الأخطاء الخاصة بـ Parsec ، على سبيل المثال عندما يكون لدي:

try (many1 parser1) <|> parser2

إذا فشل Parser1 هنا ، فسيستمر Parser2 ، لكنني أرغب في الحصول على استثناء يعمل على إحباط التحليل بالكامل.

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

المحلول

أنا تحت الانطباع بأنك تحاول إشراك MonadError لسبب خاطئ.

في ال try (many1 parser1) <|> parser2, ، السلوك الذي تحاول تجنبه ينبع من استخدام try و <|> - إذا كنت لا تحب ذلك ، فاستخدم مجموعات مختلفة. ربما تعبير مثل (many1 parser1) >> parser2 هل ستعمل بشكل أفضل بالنسبة لك؟ (هذا يتجاهل النتائج من (many1 parser1); ؛ يمكنك بالطبع الاستخدام >>= ودمج النتائج من (many1 parser1) مع تلك من parser2.)


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

فحص أوثق للتفاعل التحليلي / monaderror. أخشى أن الأمر فوضوي بعض الشيء وما زلت غير متأكد حقًا النهج الأصلي.

أولاً ، لاحظ ذلك ليس صحيحًا أن نقول إن Parsec مثال على Monaderror. Parsec هو الموناد الناتج عن التحليل عندما يكون الموناد الداخلي هو الهوية ؛ ينتج Parsect حالات من Monaderror إذا وفقط إذا تم إعطاؤه موناد داخلي وهو نفسه مثال على Monaderror للعمل معه. الشظايا ذات الصلة من تفاعلات GHCI:

> :i Parsec
type Parsec s u = ParsecT s u Identity
    -- Defined in Text.Parsec.Prim
-- no MonadError instance

instance (MonadError e m) => MonadError e (ParsecT s u m)
  -- Defined in Text.Parsec.Prim
-- this explains why the above is the case
-- (a ParsecT-created monad will only become an instance of MonadError through
-- this instance, unless of course the user provides a custom declaration)

التالي، دعنا نمتلك أنفسنا مثالًا عاملاً مع Catcherror و RACSECT. النظر في تفاعل GHCI:

> (runParserT (char 'a' >> throwError "some error") () "asdf" "a" :: Either String (Either ParseError Char)) `catchError` (\e -> Right . Right $ 'z')
Right (Right 'z')

يبدو أن التعليق التوضيحي نوعًا ضروري (يبدو أن هذا منطقيًا بالنسبة لي ، لكنه ليس له صلة بالسؤال الأصلي ، لذلك لن أحاول توضيح). يتم تحديد نوع التعبير كله بواسطة GHC على النحو التالي:

Either String (Either ParseError Char)

لذلك ، لدينا نتيجة تحليل منتظم - Either ParseError Char - ملفوفة في Either String موناد بدلاً من المعتاد Identity موناد. حيث Either String هو مثال MonadError, ، يمكننا ان نستخدم throwError / catchError, ، لكن المعالج انتقل إلى catchError يجب بالطبع أن تنتج قيمة من النوع الصحيح. هذا ليس مفيدًا جدًا للخروج من روتين التحليل ، أخشى.

العودة إلى رمز المثال من السؤال. هذا يفعل شيء مختلف قليلا. دعنا نفحص نوع ret كما هو محدد في السؤال:

forall (m :: * -> *) a.
(Monad m) =>
m (Either [Char] (Either ParseError a))

(وفقًا لـ GHCI ... لاحظ أنه كان عليّ رفع تقييد الشكل المونومبيمي مع {-# LANGUAGE NoMonomorphismRestriction #-} للحصول على ترجمة الرمز بدون تعليقات الكتابة.)

هذا النوع هو تلميح فيما يتعلق بإمكانية القيام بشيء مسلية معه ret. ها نحن ذا:

> runParserT ret () "asdf" "a"
Right (Left "some error")

بعد فوات الأوان ، المعالج الممنوح ل catchError ينتج قيمة باستخدام unexpected, ، بالطبع ، سيكون (قابل للاستخدام) محللًا ... وأخشى أنني لا أرى كيف أضع ذلك في شيء مفيد للخروج من عملية التحليل.

نصائح أخرى

إذا كنت تحاول تصحيح محلل نصفي لاستكشاف الأخطاء وإصلاحها ، فربما يكون من الأسهل استخدام error, Debug.Trace, أو ما لا.

من ناحية أخرى ، إذا كنت بحاجة إلى إنهاء التحليل على بعض المدخلات كجزء من برنامجك الفعلي ، ولكنه لا يفعل ذلك بسبب أ try (...) <|> بناء ، ثم لديك علة في منطقك ويجب أن تتوقف وإعادة التفكير في القواعد ، بدلاً من اختراقها مع معالجة الأخطاء.

إذا كنت تريد أن ينتهي المحلل المحلل على مدخلات معينة بعض الوقت ، ولكن ليس الآخرين ، فلا شيء مفقود من دفق الإدخال الخاص بك (ويجب إضافته) أو أن المحلل ليس هو الحل لمشكلتك.

إذا كنت تريد أن يتعافى المحلل المحلل بأمان من الأخطاء غير المميتة ويستمر في المحاولة عندما يكون ذلك ممكنًا ، ولكنه ينتهي بخطأ عندما لا يمكن الاستمرار ، فقد ترغب ... في التفكير في شيء آخر غير Parsec ، لأنه غير مصمم حقًا من أجل هذا. أعتقد أن مكتبة Haskell Parser Combinator من جامعة Utrechell تدعم هذا النوع من المنطق بسهولة أكبر.

تعديل: بقدر ما يكون Parsec نفسه مثال MonadError يذهب-نعم ، والخطأ الخاص به معالجة هذه الوظيفة. ما تحاول القيام به هو مكدس أ ثانيا خطأ Monad فوق Parsec ، وربما تواجه مشكلة لأنه من المحرج عمومًا التمييز بين محولات Monad التي "زائدة" بهذه الطريقة. إن التعامل مع مونادس متعددة من الولايات هو أكثر شهرة ، وهذا هو السبب في أن Parsec (موناد الدولة أيضًا) يوفر وظائف لعقد حالة مخصصة.

وبعبارة أخرى ، فإن Parsec كونه خطأ لا يساعدك على الإطلاق ، وفي الواقع يكون في الغالب بمعنى جعل مشكلتك أكثر صعوبة.

إذا كنت بحاجة إلى إنهاء التحليل على بعض المدخلات كجزء من برنامجك الفعلي ، ولكنه لا يفعل ذلك بسبب تجربة (...) <|> القواعد الخاصة بك ، بدلاً من الاختراق حولها مع معالجة الأخطاء.

إذا كنت تريد أن ينتهي المحلل المحلل على مدخلات معينة بعض الوقت ، ولكن ليس الآخرين ، فلا شيء مفقود من دفق الإدخال الخاص بك (ويجب إضافته) أو أن المحلل ليس هو الحل لمشكلتك.

تعتمد هذه الإجابة على افتراض أن المشكلة تكمن في القواعد. ولكن إذا كنت أستخدم القواعد النحوية لإطعام برنامج التحويل البرمجي ، فهناك أخطاء أخرى لا يمكن للقواعد التعامل معها. دعنا نقول مرجعًا متغيرًا ، إلى متغير لم يتم تعريفه. ويتم تحديد اللغة على أنها تمريرة واحدة ، ويتم تقييم المتغيرات كما واجهت. ثم ، القواعد على ما يرام. التحليل على ما يرام. ولكن نتيجة لتقييم ما تم تحديده في القواعد ، حدث خطأ ، أو "فشل" أو "غير متوقع" أو غير كافٍ للتعامل مع هذه المشكلة. سيكون من الجيد أن يكون لديك وسيلة لإحباط التحليل دون اللجوء إلى معالجة الأخطاء ذات المستوى الأعلى.

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