سؤال

لننظر في نوع البيانات مع العديد من البنائين:

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 لكل مُنشئ. ولكن من الواضح أنه لا يزال يترك شيئًا مرغوبًا فيه.

في بعض الحالات ، ستساعد مكتبة "الخردة من الغلاية الخاصة بك".

http://www.haskell.org/haskellwiki/SCRAP_YOUR_BOILERPLATE

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

في هذا المثال السعيد ، لن أستخدم حل Dave Hinton فحسب ، بل سأصفع براغما مضمّنة على الوظيفة الإضافية f.

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