في هاسكل ، لماذا لا تكون الأنماط غير الشاملة أخطاء في وقت التجميع؟

StackOverflow https://stackoverflow.com/questions/3804484

سؤال

هذه متابعة لماذا أحصل على "أنماط غير شاملة في الوظيفة ..." عندما استحوذت على وظيفة هاسكل الفرعية؟

أنا أفهم ذلك باستخدام -Wall, ، يمكن أن تحذر GHC من الأنماط غير الشاملة. أتساءل ما هو السبب وراء عدم جعلها خطأ في وقت الترجمة بشكل افتراضي نظرًا لأنه من الممكن دائمًا تحديد وظيفة جزئية بشكل صريح:

f :: [a] -> [b] -> Int
f [] _  = error "undefined for empty array"
f _ []  = error "undefined for empty array"
f (_:xs) (_:ys) = length xs + length ys

السؤال ليس خاصًا بـ GHC.

هل هذا بسبب...

  • لا أحد يريد إنفاذ مترجم هاسكل لأداء هذا النوع من التحليل؟
  • يمكن أن يجد البحث عن نمط غير شامل بعض الحالات ولكن ليس كل الحالات؟
  • تعتبر الوظائف المحددة جزئيًا شرعية وتستخدم في كثير من الأحيان بما يكفي لعدم فرض نوع البناء الموضح أعلاه؟ إذا كان هذا هو الحال ، هل يمكنك أن تشرح لي لماذا تكون الأنماط غير الشاملة مفيدة/شرعية؟
هل كانت مفيدة؟

المحلول

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

fac 0 = 1
fac n | n > 0 = n * fac (n-1)

أن هذا غير شامل (الأرقام السلبية لا تتطابق مع أي حالة) لا يهم حقًا الاستخدام النموذجي للوظيفة العليا.

كما قد لا يكون من الممكن عمومًا أن تقرر المترجم إذا كانت تطابق نمط شاملة:

mod2 :: Integer -> Integer
mod2 n | even n = 0
mod2 n | odd n  = 1

هنا يجب تغطية جميع الحالات ، ولكن ربما لا يمكن للمترجم اكتشافه. نظرًا لأن الحراس يمكن أن يكونوا معقدين بشكل تعسفي ، لا يمكن للمترجم دائمًا أن يقرر ما إذا كانت الأنماط شاملة. بالطبع من الأفضل كتابة هذا المثال otherwise, ، لكنني أعتقد أنه ينبغي أيضًا تجميعها في شكلها الحالي.

نصائح أخرى

يمكنك استخدام -Werror لتحويل التحذيرات إلى أخطاء. لا أعرف ما إذا كان يمكنك تحويل الأنماط غير الشاملة فقط إلى أخطاء ، آسف!

أما للجزء الثالث من سؤالك:

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

مثال لعبة:

duplicate :: [a] -> [a]
duplicate [] = []
duplicate (x:xs) = x : x : (duplicate xs)

removeDuplicates :: Eq a => [a] -> [a]
removeDuplicates [] = []
removeDuplicates (x:y:xs) | x == y = x : removeDuplicates xs

الآن من السهل جدًا رؤية ذلك removeDuplicates (duplicate as) يساوي as (كلما كان نوع العنصر في Eq)، ولكن بشكل عام duplicate (removeDuplicates bs) سوف تتعطل ، لأن هناك عددًا فرديًا من العناصر أو العناصر المتتالية تختلف. إذا لم يعطل ذلك ، فذلك بسبب bs تم إنتاجه بواسطة (أو كان يمكن إنتاجه بواسطة) duplicate في المقام الأول!.

لذلك لدينا القوانين التالية (غير صالحة هاسكل):

removeDuplicates . duplicate == id
duplicate . removeDuplicates == id (for values in the range of duplicate)

الآن ، إذا كنت ترغب في منع الأنماط غير الشاملة هنا ، فيمكنك القيام بذلك removeDuplicates إرجاع Maybe [a], أو إضافة رسائل خطأ للحالات المفقودة. يمكنك حتى أن تفعل شيئًا على غرار

newtype DuplicatedList a = DuplicatedList [a]

duplicate :: [a] -> DuplicatedList a
removeDuplicates :: Eq a => DuplicatedList a -> [a]
-- implementations omitted

كل هذا ضروري ، لأنه لا يمكنك بسهولة التعبير عن "أن تكون قائمة بطول حتى ، مع أزواج متتالية من العناصر متساوية" في نظام نوع Haskell (إلا إذا كنت Oleg :)

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

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