haskell: создание суперкласса Num

Я хочу сделать суперкласс Num, называемый Linear

class Linear a where 
  add :: a -> a -> a

instance (Num a) => Linear a where
  add = (+)

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

Illegal instance declaration for `Linear 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 -XFlexibleInstances if you want to disable this.)
In the instance declaration for `Linear a'

Насколько я понимаю, в строке instance (Num a) => Linear a where что-то не так. (Он компилируется, если я использую флаги: -XFlexibleInstances -XUndecidableInstances)

Есть ли способ добиться этого без использования этих страшных флагов? (и что в мире неразрешимого в коде выше??)

ОБНОВЛЕНИЕ: добавлен полиномиальный тип в линейный.

newtype Polynomial a = Polynomial (a,[a]) deriving Show-- list of coeffients 

instance (Linear a) => Linear (Polynomial a)
         where 
           add (Polynomial (c1, l1)) (Polynomial (c2, l2))
             = Polynomial (add c1 c2, zipWith (add) l1 l2)

p1 = Polynomial (0, [3,4,5])
p2 = Polynomial (0, [])

main = putStrLn $ show ((add p1 p2):: Polynomial Int)

После добавления полинома он не компилируется даже с этими флагами и выдает ошибку:

Overlapping instances for Linear (Polynomial Int)
  arising from a use of `add'
Matching instances:
  instance Num a => Linear a -- Defined at Algebra.hs:22:10-28
  instance Linear a => Linear (Polynomial a)
    -- Defined at Algebra.hs:25:10-44
In the first argument of `show', namely
  `((add p1 p2) :: Polynomial Int)'
In the second argument of `($)', namely
  `show ((add p1 p2) :: Polynomial Int)'
In the expression: putStrLn $ show ((add p1 p2) :: Polynomial Int)

person Karan    schedule 26.03.2012    source источник
comment
не могли бы вы указать, зачем они нужны; а неразрешимость пугает :)   -  person Karan    schedule 26.03.2012
comment
В Haskell многие вещи имеют страшные имена, о которых в других языках никто бы не беспокоился ни секунды.   -  person leftaroundabout    schedule 26.03.2012


Ответы (2)


Языковой отчет не допускает экземпляров формы instance Class a where..., поэтому единственный способ избежать FlexibleInstances (что по крайней мере не страшно) - это использовать обертку newtype,

newtype LinearType a = Linear a

liftLin2 :: (a -> b -> c) -> LinearType a -> LinearType b -> LinearType c
liftLin2 op (Linear x) (Linear y) = Linear (op x y)

instance Num a => Linear (LinearType a) where
    add = liftLin2 (+)

Фу.

Расширение UndecidableInstances необходимо, поскольку ограничение Num a не меньше заголовка экземпляра (оно использует переменные одного и того же типа одинаковое количество раз), поэтому компилятор не может заранее доказать, что проверка типов завершится. Таким образом, вы должны пообещать компилятору, что проверка типов завершится, чтобы он принял программу (на самом деле она не будет зацикливаться с GHC, у которого есть стек контекста, который контролирует глубину рекурсии средства проверки типов, поэтому, если проверка типов не t закончит достаточно скоро, компиляция завершится ошибкой с «превышением стека контекста» - вы можете установить размер с помощью -fcontext-stack=N).

Это расширение звучит гораздо страшнее, чем есть на самом деле. По сути, все, что он делает, это говорит компилятору: «Поверьте мне, проверка типов будет завершена», поэтому компилятор запустится, не зная наверняка, что он завершится.

Но чего вы пытаетесь достичь? То, что у вас есть на данный момент,

instance (Num a) => Linear a where
  add = (+)

говорит: «каждый тип является экземпляром Linear, и если вы попытаетесь использовать add для типа, а не экземпляра Num, это будет ошибка времени компиляции». Это не очень полезно. Вы не можете добавлять дополнительные экземпляры для типов, не принадлежащих Num, если только вы не включите также OverlappingInstances и, возможно, IncoherentInstances. И эти расширения жуткие, их следует использовать редко и только тогда, когда вы знаете, что делаете.

person Daniel Fischer    schedule 26.03.2012
comment
спасибо за объяснение этого, но нет ли стандартного/по умолчанию способа создания суперкласса, который не требует, чтобы пользователь давал компилятору какие-либо гарантии? - person Karan; 26.03.2012
comment
Я хочу сделать несколько экземпляров Linear, и я хочу, чтобы все это Num также было Linear. (т.е. Linear является надклассом Num и имеет больше экземпляров, чем Num) - person Karan; 26.03.2012
comment
Я подозревал такое. Однако это не то, что обычно называют суперклассом. Однако это заведет вас на OverlappingInstances территорию, а это не очень удобно. Для Polynomial имело бы смысл иметь instance Num a => Num (Polynomial a) where.... abs и signum было бы немного подозрительно, но все остальное в Num имеет смысл для полиномов. - person Daniel Fischer; 26.03.2012
comment
это означает, что все типы Linear должны иметь определение экземпляра для Num с абсурдными определениями - не очень хороший обходной путь !! - person Karan; 26.03.2012
comment
Что ж, разрешение экземпляра (по крайней мере, в GHC) принимает во внимание только часть после =›, поэтому instance (Context a) => Foo a действительно говорит, что каждый тип является экземпляром Foo. Только когда он используется, компилятор пытается найти экземпляр Context a. Это неинтуитивно, но на это есть веские причины. - person Daniel Fischer; 26.03.2012
comment
Вы можете предоставить экземпляры для Linear, которые не являются экземплярами Num с OverlappingInstances. Иногда это правильно, может быть, здесь. - person Daniel Fischer; 26.03.2012
comment
использование OverlappingInstances работает, но вся эта работа только для того, чтобы получить суперкласс/супертип, заставляет меня задуматься, не следует ли мне делать это на языке с динамической типизацией :) - person Karan; 26.03.2012
comment
@Karan - меня это тоже раньше беспокоило, но вам действительно нужен instance Linear для каждого Num? Обычно я просто пишу экземпляры для Int и Double и, возможно, Integer, и этого достаточно. Или вы можете использовать метапрограммирование (CPP или шаблон Haskell) для создания объявлений экземпляров, но обычно это не стоит проблем. - person John L; 26.03.2012
comment
Забыл упомянуть, но если вы используете новый GHC, вы можете использовать расширение DefaultSignatures при определении Linear, чтобы делать то, что вы хотите. - person John L; 26.03.2012
comment
Честно говоря, я бы сказал, что введение суперклассов для классов типов, которые вы не контролируете, просто гораздо более неудобно, чем когда вы их контролируете. - person Louis Wasserman; 26.03.2012
comment
@JohnL - у меня нет конкретной реальной цели, к которой я стремился. Я просто пытался немного посчитать и хотел, чтобы уже существующие и определенные пользователем (мной) экземпляры Num автоматически становились частью Linear. И я попробую DefaultSignature, хотя я не могу найти веб-страницу, на которой написано, что это такое (google возвращает некоторые записи в списке рассылки). Можете ли вы указать мне на один? - person Karan; 26.03.2012
comment
@Karan: извините, документы GHC находятся по адресу haskell.org/ghc/docs/7.4.1/html/users_guide/, см. раздел 7.6.1.4. Вам все равно придется писать instance Linear Int where; instance Linear Float where и т. д., но все методы будут заполнены сигнатурой по умолчанию. - person John L; 26.03.2012
comment
@JohnL: Спасибо, эта страница кажется очень полезной. - person Karan; 26.03.2012

Существует предложение разрешить объявление суперклассов. Насколько я знаю, это еще не реализовано, но поскольку GHC имеет открытый исходный код, вы можете изменить это, если хотите;)

person fuz    schedule 26.03.2012
comment
можете ли вы предложить обходной путь в то же время (который не включает создание всего типа Num (т.е. Int, Float и т. д.) экземпляром Linear вручную) - person Karan; 26.03.2012
comment
Вопрос в том, что вы действительно хотите сделать. В зависимости от вашей проблемы могут быть разные решения вашей проблемы. - person fuz; 26.03.2012