2014-12-19 2 views
2

Это прекрасно работает:Исключение для "экземпляра (Num а) => YesNo а, где" код строки

class YesNo a where 
    yesNo :: a-> Bool 

instance YesNo Bool where 
    yesNo True = True 
    yesNo _ = False 

instance YesNo [a] where 
    yesNo [] = False 
    yesNo _ = True 

instance YesNo (Maybe a) where 
    yesNo Nothing = False 
    yesNo _ = True 

Но я получаю сообщение об ошибке для кода:

instance (Num a) => YesNo a where -- error is here 
    yesNo 0 = False 
    yesNo _ = True 

Exception сообщение:

ghci> :l src 
[1 of 1] Compiling Main    (src.hs, interpreted) 

src.hs:16:21: 
    Illegal instance declaration for `YesNo a' 
     (All instance types must be of the form (T a1 ... an) 
     where a1 ... an are *distinct type variables*, 
     and each type variable appears at most once in the instance head. 
     Use FlexibleInstances if you want to disable this.) 
    In the instance declaration for `YesNo a' 
Failed, modules loaded: none. 
ghci> 

Что я сделал неправильно?

+1

Все функции могут быть упрощены. Для 'Bool':' yesNo = id', для '[a]': 'yesNo = нет. empty', для 'Maybe a':' yesNo = isJust'. – Shoe

+0

@ Джеффри 'yesNo = нет. null' – augustss

+0

@augustss Да, я все время забываю, что это 'null', а не' empty'. Я просто, похоже, испытываю глубокое отвращение к термину «null». – Shoe

ответ

2

Поскольку сообщение об ошибке уже указывает на то, есть ghc опции, которые делают ваше объявление экземпляра совершенно законно (вам придется добавить ограничение Eq a к нему, хотя, если ваш GHC является newer than 7.4.1)

ghci -XFlexibleInstances -XUndecidableInstances <your program> 

будет компилировать и работать так, как вы ожидаете.

Почему гибкие и неразрешимые экземпляры не разрешены по умолчанию?

  • классы типа открыты, они могут быть расширены в любой последующий момент. Предположим, что кто-то еще расширит Num с instance (Num a) => Num [a]), например fromInt x = [fromInt x] и покомпонентным добавлением и т. Д. Тогда у нас были бы совпадающие экземпляры: shoud yesNo [0] be True или False? Таким образом, путем расширения одного класса (Num) экземпляры какого-либо другого класса (YesNo) внезапно перекрываются. Поэтому гибкие экземпляры не разрешены по умолчанию

  • При объявлении instance (Eq a, Num a) => YesNo a, Num a не является строго меньше (проще), чем YesNo a. Мы с тобой знаем, что проверка любого типа для YesNo -ness в конечном итоге приведет к aswer, но предположим, что еще кто-то (почему бы и нет?) Не добавил бы экземпляр (YesNo a) => Num a where... Тогда тип проверки застревали в цикле: YesNo a => Num a => YesNo a ... Вот почему (потенциально) неразрешимые экземпляры не разрешены по умолчанию

Таким образом, отменяя ограничения по умолчанию на определения класса типа может хорошо работать сейчас, но может привести к серьезным царапинам головы в будущем.

+2

Он [на самом деле не работает] (http://coliru.stacked-crooked.com/a/7ccaa88cf150a2d1)« так, как вы ожидаете ». На самом деле он даже не компилируется. – Shoe

+1

@Jefffrey Вы можете описать, что он на самом деле сделает? – MathematicalOrchid

+0

Правильно, объявление экземпляра должно быть (тривиально) ограничено немного больше: 'instance (Eq a, Num a) => YesNo a'. –

5

Похоже, что вы пытаетесь сделать так, чтобы каждый тип, являющийся экземпляром Num, был автоматически экземпляром YesNo.

К сожалению, вы не можете этого сделать.

Вы можете только объявить экземпляр для определенного типа. Таким образом, вы можете объявить экземпляр для Int или Double, но вы не можете объявить экземпляр для «каждый Num».

+0

Спасибо, @MathematicsOrchid. –

+0

I.e. Должен ли я писать импликации для 'Int',' Integer', 'Float' и' Double' отдельно? Это не удобно ... :( –

+0

@Bush Да. Раздражающе, не так ли? – MathematicalOrchid

1

Попробуйте это:

instance (Num a) => YesNo a where 
    yesNo x | x == fromIntegral 0 = False 
      | otherwise = True 

Проблема заключается в том, что 0 не типа a. В любом случае из Num, однако, вы должны определить функцию fromIntegral.

+1

Числовые литералы перегружены в Haskell - целое число l 'n' (например,' 0') эквивалентно 'fromIntegral n', с типом' (Num a) => a'. Это означает, что ваше объявление экземпляра, по сути, совпадает с исходным, и для этого требуется дополнительное ограничение ('Eq a') и параметры компилятора для работы –

+0

Aha. Можете ли вы объяснить мне, почему вы можете определить экземпляр: экземпляр 'Ord а, Ь => Ord (а, б)' ... , но это не один ... –

+1

'Ord (а, б) 'имеет вид' classname (T ab) '(где конструктор' T' является оператором сопряжения), а 'Yesno a' имеет форму' classname a'. Эта «упаковка» переменных типа в конструкторе предотвращает проблемы с гибкими экземплярами, а также с неразрешимыми экземплярами (см. Мой ответ) –