Почему этот код класса типов Haskell не работает?

Пытаюсь понять классы типов Haskell. Почему не работает следующее?

{-# LANGUAGE FlexibleInstances #-}
class IntClass t
instance IntClass Int

intToIntClass  :: (IntClass r) => Int -> r
intToIntClass  x = x

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

Could not deduce (r ~ Int)
from the context (IntClass r)
  bound by the type signature for intToIntClass  :: IntClass r => Int -> r
  at f.hs:10:1-16
  `r' is a rigid type variable bound by
      the type signature for intToIntClass  :: IntClass r => Int -> r
      at f.hs:10:1
In the expression: x
In an equation for `intToIntClass t': intToIntClass  x = x

person drwowe    schedule 29.04.2012    source источник
comment
Часть (r ~ Int) верхней строки в сообщении об ошибке означает, что не удалось вывести тип r того же типа, что и Int. ~ (тильда) означает равенство типов, к которому, как только вы привыкнете, это нормально, но если вы видите это впервые, то это немного странно. Использование более естественного = для равенства типов сделало бы синтаксис Haskell неоднозначным, когда он используется в программах, а не в сообщениях об ошибках.   -  person stephen tetley    schedule 30.04.2012


Ответы (4)


Подпись

intToIntClass  :: (IntClass r) => Int -> r

означает, что intToIntClass может создавать значение любого типа, который хочет вызывающий, если этот тип принадлежит IntClass. Но реализация может производить только Int значений.

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

person Daniel Fischer    schedule 29.04.2012

Написанная вами функция говорит: «Дайте мне что-нибудь, и я верну это вам». Тип, который вы написали, говорит: «Я могу преобразовать Int в любой тип, который пожелает вызывающий, если этот тип является экземпляром класса типов IntClass».

Когда мы соединим их вместе, у нас будет функция, которая будет принимать Int от вызывающей стороны и возвращать ее обратно. За исключением того, что когда будет возвращено то же самое Int, это будет значение любого типа, который хотел бы вызывающий, если этот тип является членом класса типов IntClass. Как вход может перейти от Int к любому (ограниченному) типу, который хотел бы вызывающий? Это невозможно. Поскольку входные данные возвращаются прямо назад, тип возвращаемого значения должен быть таким же, как и тип аргумента. Средство проверки типов делает вывод об этом из вашего кода, но затем не может согласовать этот вывод с тем, что вы называете тип вывода r. Средство проверки типов хочет работать с вами над этим, но не может быть уверено, что r — это то же самое, что и Int, учитывая единственную гипотезу, что r является экземпляром класса типов IntClass.

Функция, похожая на ту, которую вы написали, называется fromInteger и находится в классе типов Num. Если вы хотите поговорить обо всех типах, которые вы можете создать на основе Int, то это должен быть метод вашего класса типов. Что-то типа:

class IntClass a where
  intToIntClass :: Int -> a

Вы должны определить этот метод для каждого типа, который вы хотите сделать экземпляром IntClass, и получить желаемый тип intToIntClass :: IntClass r => Int -> r. Этот полиморфизм возвращаемого типа критически зависит от каждого участвующего типа, имеющего соответствующее определение intToIntClass.

person Anthony    schedule 29.04.2012

Ваш typeclass в порядке. Например, это работает:

intId :: IntClass r => r -> r
intId = id

Проблема в том, что сигнатура типа intToIntClass говорит, что она сопоставляется с Int с любым элементом IntClass. Компилятор не может доказать, что Int является единственным элементом IntClass, поэтому, поскольку тело intToIntClass жестко возвращает Int, оно недостаточно полиморфно.

person bdonlan    schedule 29.04.2012
comment
Ах, это имеет смысл. Моя ошибка заключалась в том, что я думал, что сигнатура функции делает что-то вроде приведения интерфейса на других языках. - person drwowe; 30.04.2012

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

instance IntClass Integer

Тогда ваша реализация intToIntClass также обещает иметь тип (IntClass Integer => Int -> Integer). Но ваша реализация противоречит этому. Компилятор предполагает, что классы типов являются «открытыми», что означает, что

  1. новые экземпляры могут появиться в будущем, и
  2. сегодняшний код не должен изменить поведение в таком будущем

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

Комбинация (1.) и (2.) значительно упрощает размышления и рассуждения о коде на Haskell. Гораздо меньше предостережений, когда вы знаете, что сегодняшний новый экземпляр не может испортить вчерашний код и что сегодняшний код защищен от завтрашнего нового экземпляра.

person Chris Kuklewicz    schedule 30.04.2012