هل يمكن تعريف آلية الحراسة المخصصة في هاسكل؟
-
20-09-2019 - |
سؤال
إذا نظرت إلى المثال 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