سؤال

إذا نظرت إلى المثال catches:

 f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex),
                     Handler (\ (ex :: IOException)    -> handleIO    ex)]

يبدو catches حددت آلية مخصصة لتتناسب مع الأنماط (نوعين الاستثناءات). هل أنا مخطئ ، أو هل يمكن تعميم هذا للسماح لأحد بتحديد وظيفة يمكن أن تأخذ وظائف Lambda التي تتطابق مع نمط معين؟

تحرير: لمعلوماتك أدناه هو مصدر GHC للصيد. إذا كان بإمكان شخص ما إلقاء بعض الضوء على كيفية عمل هذا ، فسيكون ذلك رائعًا.

catches :: IO a -> [Handler a] -> IO a
catches io handlers = io `catch` catchesHandler handlers

catchesHandler :: [Handler a] -> SomeException -> IO a
catchesHandler handlers e = foldr tryHandler (throw e) handlers
    where tryHandler (Handler handler) res
              = case fromException e of
                Just e' -> handler e'
                Nothing -> res
هل كانت مفيدة؟

المحلول

هذا ال متغيرات نوع النطاق امتداد GHC في العمل. اتبع الرابط لمعرفة المزيد.

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

كيف يعمل هذا المثال بالذات؟ يغوص في مصادر مكتبة "قاعدة" لمعرفة ذلك:

class (Show e) => Exception e where
    toException   :: e -> SomeException
    fromException :: SomeException -> Maybe e

data SomeException = forall e . Exception e => SomeException e

instance Exception IOException where
    toException = IOException
    fromException (IOException e) = Just e
    fromException _ = Nothing

instance Exception ArithException where
    toException = ArithException
    fromException (ArithException e) = Just e
    fromException _ = Nothing

نحن نرى ذلك IOException و ArithException هي أنواع مختلفة تنفذ typeclass Exception. نرى ذلك أيضًا toException/fromException هي آلية تغليف/إلغاء التغليف تسمح للمرء بتحويل قيم النوع Exception إلى/من قيم الأنواع IOException, ArithException, ، إلخ.

لذلك ، يمكن أن نكتب:

f = expr `catches` [Handler handleArith,
                    Handler handleIO]

handleArith :: ArithException -> IO ()
handleArith ex = ....

handleIO :: IOException -> IO ()
handleIO ex = ....

لنفترض أن IOException يحدث. متى catchesHandler المعالجة العنصر الأول من قائمة المعالجات ، فإنه يستدعي tryHandler, الذي يدعو fromException. من تعريف tryHandler يتبع ذلك نوع العودة من fromException يجب أن تكون هي نفس حجة handleArith. على الجانب الآخر، e هو من النوع استثناء ، وهو - (ioException ...). لذا ، يتم تشغيل الأنواع بهذه الطريقة (هذا ليس هاسيلًا صالحًا ، لكنني آمل أن تحصل على وجهة نظري):

fromException :: (IOException ...) -> Maybe ArithException

من instance Exception IOException ... يتبع على الفور أن النتيجة هي Nothing, ، لذلك يتم تخطي هذا المعالج. بنفس المنطق ، سيتم استدعاء المعالج التالي ، لأنه fromException سيعود (Just (IOException ...)).

لذلك ، استخدمت توقيع نوع handleArith و handleIO لتحديد متى سيتم استدعاء كل منهم ، و fromException/toException تأكد من أن ذلك حدث بهذه الطريقة.

إذا كنت ترغب في ذلك ، يمكنك أيضًا قيود أنواع handleIO و handleArith داخل تعريف f, ، باستخدام متغيرات نوع النطاق. يمكن القول ، هذا يمكن أن يمنحك قابلية للقراءة أفضل.

وضع اللمسات الأخيرة على متغيرات النوع المنتشر ليست لاعبين أساسيين هنا. يتم استخدامها فقط للراحة. الآلات الرئيسية للعب هذا النوع من الحيل fromException/toException والأصدقاء. تتيح لك متغيرات النوع الناطق فقط أن يكون لديك بناء جملة تشبه أنماط الحراسة بشكل أوثق.

نصائح أخرى

case () of 
  ()| foo expr1 -> handleFooCase
    | bar expr2 -> handleBarCase
    | otherwise -> blah
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top