Как сопоставить жесткие типы в экземпляре класса типов?

Я подумал, что попробую смоделировать численное интегрирование векторных величин разной размерности, и решил, что классы типов — это то, что нужно. Мне нужно было что-то, чтобы определить разницу между двумя значениями и масштабировать ее с помощью множителя (чтобы получить производную), а также иметь возможность использовать функцию расстояния.

Пока у меня есть:

class Integratable a where
    difference :: a -> a -> a
    scale :: Num b => a -> b -> a
    distance :: Num b => a -> a -> b

data Num a => Vector a = Vector1D a | Vector2D a a

instance Num a => Integratable (Vector a) where
    difference (Vector1D x1) (Vector1D x2) = Vector1D (x1 - x2)
    scale (Vector1D x) m = Vector1D (x * m)
    distance (Vector1D x1) (Vector1D x2) = x1 - x2
    difference (Vector2D x1 y1) (Vector2D x2 y2) = Vector2D (x1 - x2) (y1 - y2)
    scale (Vector2D x y) m = Vector2D (x * m) (y * m)
    distance (Vector2D x1 y1) (Vector2D x2 y2) = sqrt((x1-x2)*(x1-x2)
                                                      + (y1-y2)*(y1-y2))

К сожалению, здесь есть пара проблем, которые я не понял, как решить. Во-первых, функция scale выдает ошибки. GHC не может сказать, что m и x совместимы, так как жесткое ограничение типа Num дано в экземпляре в одном случае, а в типе Vector в другом случае... Есть ли способ указать, что x и m являются того же типа?

(На самом деле я понимаю, что даже если x и m оба являются Num, они могут не быть одним и тем же Num. Как я могу указать это? Если я не могу понять это с Num, использование Double приведет к хорошо, но я предпочитаю держать это в общих чертах.)

Аналогичная проблема с distance. Попытка указать тип возвращаемого значения Num не удалась, так как в определении экземпляра нельзя сказать, что a будет содержать значения, совместимые с b.


person Steve    schedule 22.12.2009    source источник
comment
Не могли бы вы скопировать и вставить ваш фактический исходный код, пожалуйста? Вы определяете Vector2D (a,a), но используете его так, как если бы он был определен Vector2D a a; точно так же вы используете Vector1D в нескольких местах, где вы, кажется, имеете в виду Vector2D.   -  person dave4420    schedule 22.12.2009
comment
извините, только что заметил и отредактировал   -  person Steve    schedule 22.12.2009
comment
также удалил эту часть вопроса, поскольку это было основной ошибкой. меня больше интересует первая часть, как заставить работать scale.   -  person Steve    schedule 22.12.2009
comment
Хотя это не влияет на работу кода, слово интегрируемо.   -  person Nefrubyr    schedule 22.12.2009


Ответы (2)


EDIT: Теперь мне кажется, что статья о функциональных зависимостях из HaskellWiki предоставляет ключевую информацию в наилучшей форме, которую я могу найти, поэтому я предлагаю прочитать это вместо моего ответа здесь. Однако я не удаляю остальную часть контента, так как это ясно (надеюсь), почему FD здесь полезны.


Помимо проблемы с группировкой определений, на которую указал Дэйв...

(На самом деле я понимаю, что даже если x и m оба являются Num, они могут не быть одним и тем же Num. Как я могу указать это? Если я не могу понять это с Num, использование Double приведет к хорошо, но я предпочитаю держать это в общих чертах.)

Это основная проблема, на самом деле. Скажем, вы не можете умножить Integer на Float. По сути, вам нужно, чтобы x и m в масштабе были одного типа.

Кроме того, аналогичная проблема возникает с расстоянием, но с дополнительной сложностью, заключающейся в том, что для sqrt требуется аргумент Floating. Так что я думаю, вам нужно было бы упомянуть об этом тоже где-то. (скорее всего на экземпляре, наверное).

EDIT: ОК, так как sqrt работает только со значениями Floating, вы можете свернуть класс типов для тех, кто при необходимости преобразует Floats в Doubles.

Другая идея связана с наличием класса типов Scalable:

data Vector a = Vector1D a | Vector2D a a deriving (Show)                       

class Scalable a b | a -> b where
  scale :: a -> b -> a

instance (Num a) => Scalable (Vector a) a where
  scale (Vector1D x)   m = (Vector1D (x * m))
  scale (Vector2D x y) m = (Vector2D (x * m) (y * m))

При этом используется так называемая функциональная зависимость в определении Scalable. На самом деле, пытаясь вспомнить синтаксис для этого, я нашел эту ссылку... Итак, я думаю, вам следует игнорировать мою неудачную попытку быть полезным и прочитать там информацию о качестве. ;-)

Я думаю, вы должны быть в состоянии использовать это, чтобы решить вашу первоначальную проблему.

person Michał Marczyk    schedule 22.12.2009
comment
Действительно... не думал об этой проблеме с sqrt. Но я не могу просто поставить scale :: a -> a -> a, потому что a является векторной величиной, а scale масштабирует вектор с помощью скалярной величины. - person Steve; 22.12.2009
comment
Возможно, мне нужно определить Scalar, который каким-то образом привязан к тому же типу, что и параметры Vector? Не уверен, как это сделать.. - person Steve; 22.12.2009
comment
Ой, верно. Через минуту я соответствующим образом отредактирую, хотя я думаю, что у меня может быть другая идея, поэтому я попытаюсь включить ее в редактирование. - person Michał Marczyk; 22.12.2009
comment
Я думаю, что scale:: (Num a) => Vector1D a -> a -> Vector1D a будет работать, но у меня нет под рукой Haskell, чтобы проверить. - person stonemetal; 22.12.2009
comment
Ваша Scalable идея очень хороша, надо попробовать, спасибо. - person Steve; 22.12.2009
comment
Надеюсь, это полезно для вас. Обратите внимание, что функциональные зависимости являются расширением GHC, как и классы типов с несколькими параметрами и гибкие экземпляры. Используйте -XMultiParamTypeClasses -XFunctionalDependencies -XFlexibleInstances (или соответствующие прагмы, или -fglasgow-exts) для компиляции масштабируемого кода. - person Michał Marczyk; 22.12.2009
comment
Хм, я вдруг понял, что это был не очень полезный комментарий, предыдущий - GHC сказал бы вам все это... В любом случае: счастливого взлома! :-) - person Michał Marczyk; 22.12.2009
comment
На самом деле, у вас мог бы быть класс типов для вычисления расстояния... Затем вы могли бы использовать, скажем, 2D-вектора целых чисел с манхэттенским расстоянием, если это когда-либо было вам полезно. На самом деле, сделать Vector также классом типов, требуя Scalable a и Distant a (что вы имеете в виду, что Distant не будет хорошим именем?), с (Num a) => Vector экземпляром... безумие typeclass FTW! :-) (В любом случае самые важные вопросы в конечном итоге решаются с помощью FunDeps.) - person Michał Marczyk; 23.12.2009
comment
Большое спасибо, я попробовал это после работы, и функциональные зависимости кажутся правильным путем. Пока я собираюсь держать этот трюк в заднем кармане, так как я действительно не хочу вдаваться в языковые расширения, пока я все еще изучаю основы Haskell, но я думаю, что это определенно правильный ответ. - person Steve; 23.12.2009

Чтобы исправить вторую ошибку, я думаю, вам нужно изменить порядок своих определений в объявлении экземпляра. Сначала два уравнения для difference, затем уравнения для scale, затем оба для distance.

person dave4420    schedule 22.12.2009
comment
не знал, что группировка определений имеет значение. в любом случае, это решает проблему с conflicting definitions for difference, но это не главная проблема.. до сих пор не знаю, как определить scale. - person Steve; 22.12.2009
comment
извините, я отредактировал второй вопрос, вы, ребята, слишком быстры для stackoverflow .. ;) - person Steve; 22.12.2009