Переменные неоднозначного типа с функциональными зависимостями Haskell

Я играл с FunctionalDependencies-Extension Haskell вместе с MultiParamTypeClasses. Я определил следующее:

class Add a b c | a b -> c where
    (~+) :: a -> b -> c
    (~-) :: a -> b -> c
    neg :: a -> a
    zero :: a

который отлично работает (я пробовал с экземплярами для Int и Double с конечной целью иметь возможность добавлять Int и Doubles без явного преобразования).

Когда я пытаюсь определить реализации по умолчанию для neg или (~-) следующим образом:

class Add ...
    ...
    neg n = zero ~- n

GHCi (7.0.4) говорит мне следующее:

Ambiguous type variables `a0', `b0', `c0' in the constraint:
  (Add a0 b0 c0) arising from a use of `zero'
Probable fix: add a type signature that fixes these type variable(s)
In the first argument of `(~-)', namely `zero'
In the expression: zero ~- n
In an equation for `neg': neg n = zero ~- n

Ambiguous type variable `a0' in the constraint:
  (Add a0 a a) arising from a use of `~-'
Probable fix: add a type signature that fixes these type variable(s)
In the expression: zero ~- n
In an equation for `neg': neg n = zero ~- n

Я думаю, что понимаю проблему здесь. GHC не знает, какой ноль использовать, поскольку это может быть любой ноль, дающий что-либо, что, в свою очередь, передается в ~-, о котором мы знаем только, что он имеет a в правильном аргументе и дает a.

Итак, как я могу указать, что это должен быть ноль из того же самого экземпляра, т.е. как я могу выразить что-то вроде:

neg n = (zero :: Add a b c) ~- n

Я думаю, что a, b и c здесь не a b c формируют окружающий класс, а любые a b и c, так как я могу выразить тип, который является ссылкой на локальные переменные типа?


person scravy    schedule 04.05.2012    source источник


Ответы (2)


Вытяните neg и zero в суперкласс, который использует только один тип:

class Zero a where
    neg :: a -> a
    zero :: a

class Zero a => Add a b c | a b -> c where
    (~+) :: a -> b -> c
    (~-) :: a -> b -> c

Дело в том, что, по-вашему, zero :: Int может быть zero из Add Int Int Int, или zero из Add Int Double Double, и нет способа устранить неоднозначность между ними, независимо от того, имеете ли вы в виду это изнутри реализация по умолчанию, объявление экземпляра или обычный код.

(Вы можете возразить, что zero из Add Int Int Int и zero из Add Int Double Double будут иметь одно и то же значение, но компилятор не может знать, что кто-то не собирается определять Add Int Char Bool в другом модуле и присваивать zero другое значение.)

Разделив класс типов на два, мы устраним двусмысленность.

person dave4420    schedule 04.05.2012

Вы не можете выразить функцию zero как часть класса Add. Все переменные типа в объявлении класса должны встречаться в объявлении типа для каждой функции класса; в противном случае Haskell не сможет решить, какой экземпляр типа использовать, потому что у него слишком мало ограничений.

Другими словами, zero не является свойством класса, который вы моделируете. По сути, вы говорите: «Для любых трех типов a, b, c должно существовать нулевое значение для типа a», что не имеет смысла; вы можете выбрать любые b и c, и это решит проблему, поэтому b и c совершенно непригодны для использования, поэтому, если у вас есть Add Int Int Int или Add Int (Maybe String) Boat, Haskell не знает, какой экземпляр предпочесть. Вам нужно отделить свойство отрицания и "нульности" в отдельный класс(ы) типов:

class Invertible a where
  invert :: a -> a

neg :: Invertible a => a -> a
neg = invert

class Zero a where
  zero :: a

class Add a b c | a b -> c where
  (~+) :: a -> b -> c
  (~-) :: a -> b -> c

Я не понимаю, зачем тогда вообще нужны ограничения Invertible и Zero в Add; вы всегда можете складывать числа, не зная их нулевого значения, не так ли? Зачем указывать neg как требование для ~+; есть некоторые числа, которые должны быть добавлены без того, чтобы они были отрицательными (например, натуральные числа)? Просто держите задачи класса отдельно.

person dflemstr    schedule 04.05.2012