Возврат чего-то из другого класса типов B в функцию класса типов A в Haskell

Я делаю забавный проект, в котором пытаюсь переделать некоторые базовые типы данных и концепции из Java. В настоящее время я занимаюсь итераторами.

Мой подход заключается в следующем: (1) перевести интерфейсы в классы типов (2) объявить пользовательские типы данных и экземпляры для реальных реализаций.

Поэтому я создал следующие классы типов:

class Iterator it where
    next :: it e -> (it e, e)
    hasNext :: it e -> Bool

class Iterable i where
    iterator :: Iterator it => i e -> it e

class Iterable c => Collection c where
    add :: c e -> e -> c e

Да, я пытаюсь перевести концепцию итераторов (в данном случае это просто рамка вокруг фактического списка).

Вот моя реализация простого списка:

data LinkedList e = Element e (LinkedList e) | Nil
    deriving (Show, Eq)

instance Collection LinkedList where
    add Nil e = Element e Nil
    add (Element x xs) e = Element x $ add xs e

Я исключил другие функции, такие как remove, contains, addAll для упрощения.

Вот итератор:

data LinkedListIterator e = It (LinkedList e)

instance Iterator LinkedListIterator where
    hasNext (It Nil) = False
    hasNext (It _) = True
    next (It (Element x xs)) = (It xs, x)

Наконец, экземпляр для Iterable LinkedList отсутствует. Вот что я делаю:

instance Iterable LinkedList where
    iterator list = It list

Функция итератора оборачивает список в LinkedListIterator и возвращает его. Здесь GHC заявляет об ошибке:

Could not deduce (it ~ LinkedListIterator)
from the context (Iterator it)
  bound by the type signature for
             iterator :: Iterator it => LinkedList e -> it e

  `it' is a rigid type variable bound by
       the type signature for
         iterator :: Iterator it => LinkedList e -> it e

Expected type: it e
  Actual type: LinkedListIterator e

чего я не совсем понимаю. Существует экземпляр Iterator для LinkedListIterator, так почему же ожидаемый тип "it e" несовместим с фактическим типом "LinkedListIterator e" (который, насколько я понимаю, является Iterator e) . Что вообще означает тильда (~)? Что такое переменная жесткого типа?

EDIT: я изменил заголовок с Translating Java Types into Haskell types: type deduction fail due to rigid type variable на Returning something from another type class B in function of type class A in Haskell, поскольку считаю, что моя реальная проблема связана с проблемой возврата чего-то типа класса B из типа класса A в функция-итератор.

РЕШЕНИЕ: благодаря ответам я изменил свой код на приведенную ниже версию. Тем не менее, я весело провел время, читая Typeclassopedia, и могу только рекомендовать его. Как уже было сказано, нужно выучить идиомы Haskell.

data Iterator c e = Next (Iterator c e, e) | Empty
    deriving (Show, Eq)

next :: Iterator c e -> (Iterator c e, e)
next (Next (i, e)) = (i, e)

hasNext :: Iterator c e -> Bool
hasNext Empty = False
hasNext _ = True

class Iterable i where
    iterator :: i e -> Iterator (i e) e

instance Iterable LinkedList where
    iterator Nil = Empty
    iterator (Element x xs) = Next (iterator xs, x)

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


Ответы (1)


iterator :: Iterator it => i e -> it e

Это означает, что вызывающий может выбрать it как угодно, при условии, что он реализует Iterator. С другой стороны, это обещание iterator работать для всех типов it, которые реализуют Iterator.

Ваша реализация предоставляет LinkedListIterator независимо от того, что запрашивает вызывающий.

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

Это отличается от Java, где вызывающая сторона выбирает классы входных данных, а вызываемая сторона выбирает класс выходных данных. В Haskell вызывающая сторона выбирает типы ввода и вывода.

~ означает равенство типов.


Некоторые более широкие моменты. (И я ценю, что вы пытаетесь перевести идиомы Java в Haskell, но вам нужно выучить идиомы Haskell.)

  1. Иногда вы не хотите возвращать значение, реализующее класс типов, вы просто хотите вернуть значение.

    Если бы Iterator был не классом типов, а типом данных...

    data Iterator e = Iterator {next    :: (Iterator e, e),
                                hasNext :: Bool}
    

    ... тогда вы можете просто вернуть значение типа Iterator и не беспокоиться о разных реализациях классов типов.

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

  2. Лучшим определением Iterator было бы

    data Iterator e = Iterator {next :: Maybe (Iterator e, e)}
    

    Это лучше, потому что вам будет труднее запросить следующее значение у итератора, не проверив предварительно наличие следующего значения.

  3. Мое второе определение Iterator немного похоже на ваше определение LinkedList, а также на определение стандартных списков Haskell (которые сами по себе являются связанными списками). И действительно, идиоматично использовать списки Haskell в качестве промежуточной структуры данных там, где это необходимо для итерации.

  4. Прочтите о Foldable и Traversable классы типов в Typeclassopedia. На самом деле, прочитайте Typeclassopedia, это хорошее введение в некоторые из наиболее пугающих классов типов.

person dave4420    schedule 05.02.2012
comment
Хороший ответ. Класс Iterator может добавить связанный тип, поэтому LinkedLists всегда будет использовать LinkedListIterator (хотя это очень быстро переносит нас на продвинутую территорию). - person stephen tetley; 05.02.2012
comment
Вот еще один способ подумать об этом. Полиморфизм подтипа Java соответствует экзистенциальному типу. Если у меня есть метод, который возвращает что-то типа Iterator, я говорю: «Эта функция может возвращать значение некоторого типа T, который является подтипом Iterator». Вместо этого в Haskell у меня есть универсальный квантифицированный тип; Iterator it => ... -> it означает, что «эта функция может возвращать значение любого типа t, который является экземпляром Iterator». - person Antal Spector-Zabusky; 05.02.2012