"مطابقة الأنماط" من منشئي بيانات النوع الجبري
-
26-09-2019 - |
سؤال
لننظر في نوع البيانات مع العديد من البنائين:
data T = Alpha Int | Beta Int | Gamma Int Int | Delta Int
أرغب في كتابة وظيفة للتحقق مما إذا تم إنتاج قيمتين مع نفس المُنشئ:
sameK (Alpha _) (Alpha _) = True
sameK (Beta _) (Beta _) = True
sameK (Gamma _ _) (Gamma _ _) = True
sameK _ _ = False
المحافظة sameK
ليس الكثير من المرح ، لا يمكن فحصه للصحة بسهولة. على سبيل المثال ، عند إضافة مُنشئين جدد T
, ، من السهل أن تنسى التحديث sameK
. لقد حذفت سطرًا واحدًا لإعطاء مثال:
-- it’s easy to forget:
-- sameK (Delta _) (Delta _) = True
والسؤال هو كيفية تجنب الغلاية في sameK
؟ أو كيفية التأكد من أنه يتحقق للجميع T
المُنشئون؟
الحل الذي وجدته هو استخدام أنواع بيانات منفصلة لكل من المنشئين ، مستمدة Data.Typeable
, ، وإعلان فئة من النوع الشائع ، لكني لا أحب هذا الحل ، لأنه أقل قابلية للقراءة وأيضًا مجرد نوع جبري بسيط يعمل بالنسبة لي:
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Typeable
class Tlike t where
value :: t -> t
value = id
data Alpha = Alpha Int deriving Typeable
data Beta = Beta Int deriving Typeable
data Gamma = Gamma Int Int deriving Typeable
data Delta = Delta Int deriving Typeable
instance Tlike Alpha
instance Tlike Beta
instance Tlike Gamma
instance Tlike Delta
sameK :: (Tlike t, Typeable t, Tlike t', Typeable t') => t -> t' -> Bool
sameK a b = typeOf a == typeOf b
المحلول
انظر إلى وحدة Data.data ، toConstr
وظيفة على وجه الخصوص. جنبا إلى جنب مع {-# LANGUAGE DeriveDataTypeable #-}
سيحصل هذا على حل 1 خط يعمل مع أي نوع وهو مثيل Data.Data
. لا تحتاج إلى معرفة كل SYB!
إذا ، لسبب ما (عالق مع العناق؟) ، فهذا ليس خيارًا ، فإليك اختراقًا قبيحًا للغاية وبطيئًا للغاية. إنه يعمل فقط إذا كان نوع البيانات الخاص بك Show
قادر (على سبيل المثال باستخدام deriving (Show)
- مما يعني عدم وجود أنواع وظائف في الداخل ، على سبيل المثال).
constrT :: T -> String
constrT = head . words . show
sameK x y = constrT x == constrT y
constrT
يحصل على تمثيل السلسلة من مُنشئ أقصى خارجي لـ T
القيمة من خلال إظهارها ، وتقطيعه إلى كلمات ثم الحصول على الأول. أعطي توقيعًا صريحًا من النوع حتى لا تميل إلى استخدامه على أنواع أخرى (وللتهرب من تقييد الشكل المونومبي).
بعض العيوب البارزة:
- هذا يكسر بشكل فظيع عندما يكون نوعك مُنشئات (مثل
data T2 = Eta Int | T2 :^: T2
) - إذا كان لدى بعض مُنشئاتك بادئة مشتركة ، فسيكون ذلك أبطأ ، حيث يجب مقارنة جزء أكبر من الأوتار.
- لا يعمل على أنواع مع العرف
show
, ، مثل العديد من أنواع المكتبات.
ومع ذلك ، هو Haskell 98 ... ولكن هذا عن الشيء الجميل الوحيد الذي يمكنني قوله حول هذا الموضوع!
نصائح أخرى
طريقة أخرى ممكنة:
sameK x y = f x == f y
where f (Alpha _) = 0
f (Beta _) = 1
f (Gamma _ _) = 2
-- runtime error when Delta value encountered
خطأ وقت التشغيل ليس مثاليًا ، ولكنه أفضل من إعطاء الإجابة الخاطئة بصمت.
ستحتاج إلى استخدام مكتبة الأدوية مثل الخردة من Boilerplate أو Uniplate للقيام بذلك بشكل عام.
إذا كنت لا تريد أن تكون ثقيلًا جدًا ، فيمكنك استخدام حل Dave Hinton ، مع اختصار السجل الفارغ:
...
where f (Alpha {}) = 0
f (Beta {}) = 1
f (Gamma {}) = 2
لذلك ليس عليك أن تعرف عدد Args لكل مُنشئ. ولكن من الواضح أنه لا يزال يترك شيئًا مرغوبًا فيه.
في بعض الحالات ، ستساعد مكتبة "الخردة من الغلاية الخاصة بك".
يمكنك بالتأكيد استخدام الأدوية العامة للتخلص من الغلاية. الكود الخاص بك هو مثال كتاب مدرسي لماذا أنا (والعديد من الآخرين مطلقا استخدم ال _
Wildcard في المستوى العلوي). في حين أنه من الممل أن تكتب جميع الحالات ، إلا أنها أقل شاقة من التعامل مع الأخطاء.
في هذا المثال السعيد ، لن أستخدم حل Dave Hinton فحسب ، بل سأصفع براغما مضمّنة على الوظيفة الإضافية f
.