Ошибка Haskell Parse в шаблоне при получении экземпляра из Eq

Я изучаю Haskell с помощью "Learn You a Haskell for Great Good!" и в настоящее время я пытаюсь понять классы типов и экземпляры. LYAH предоставляет пример, в котором определен тип с именем TrafficLight. следующее:

data TrafficLight = Red | Yellow | Green

Теперь предполагается, что TrafficLight является экземпляром Eq, демонстрирующим следующее поведение:

instance Eq TrafficLight where
    Red == Red = True
    Green == Green = True
    Yellow == Yellow = True
    _ == _ = False

Чтобы понять, как это работает, я написал свой собственный файл с именем Shop.hs, где я пытаюсь переопределить поведение Eq для моего ItemSlot.

module Shop where

type Number = Int

data Item =
          BellPepper
        | Cabbage
        | Carrot
        | Lettuce
        | Onion
        | Potato
        | Tomato
        deriving (Show, Read, Eq)

data ItemSlot = ItemSlot {
        item :: Item,
        number :: Number
        } deriving (Show)

instance Eq ItemSlot where
        ((item a) == (item a)) = True -- line that contains the error
        _ == _ = False

Однако, если я загружаю файл в GHCi, я получаю следующую ошибку:

Prelude> :l Shop.hs 
[1 of 1] Compiling Shop             ( Shop.hs, interpreted )

Shop.hs:21:11: Parse error in pattern: item
Failed, modules loaded: none.

(Я должен признать, что я довольно смущен относительно того, какой здесь правильный синтаксис - это item a или просто item? Использование только item завершается с той же ошибкой и использованием большего количества круглых скобок - как и ответ в другом вопросе, подобном этому, на SO - тоже не помогает)

Я предполагаю, что я не могу использовать функцию item, предоставляемую синтаксисом записи, который используется в ItemSlot, но, тем не менее, я не знаю, как решить эту проблему.


person Anju Fabulina    schedule 26.08.2012    source источник


Ответы (2)


Шаблоны обычно начинаются с конструктора. Конструктор для типа ItemSlot — это ItemSlot, поэтому вы должны использовать его:

instance Eq ItemSlot where
    ItemSlot item a == ItemSlot item' a' = -- use item, item', a, and a'

С другой стороны, поскольку вы определили ItemSlot как запись, существует так называемый синтаксис записи для шаблонов. Вы можете связывать переменные по имени, а не по положению:

instance Eq ItemSlot where
    ItemSlot { item = foo, number = a } == ItemSlot { item = foo', number = a' }
        = -- use foo, foo', a, and a'

Вы можете, конечно, затенить имена, если вы не возражаете против путаницы:

instance Eq ItemSlot where
    ItemSlot { item = item, number = a } == ItemSlot { item = item', number = a' }
        = -- use item, item', a, and a'

Для удобства шаблоны в Haskell могут быть вложены друг в друга; поэтому, если вы хотите сопоставить ItemSlots, которые оба имеют BellPeppers, например, вы можете написать

instance Eq ItemSlot where
    ItemSlot BellPepper a == ItemSlot BellPepper a' = True
    -- or, equivalently
    ItemSlot { item = BellPepper } == ItemSlot { item = BellPepper } = True

хотя обычно вы делегируете сравнение Item экземпляру Eq для Item.

person Daniel Wagner    schedule 26.08.2012
comment
Спасибо за вашу помощь, ваш первый пример делает это для меня (я хочу сравнить только один и тот же элемент, а не один и тот же номер). Наверное, я бы никогда не догадался об этом сам. - person Anju Fabulina; 26.08.2012
comment
Просто добавьте к этому: вы можете использовать функцию доступа item (или любую другую функцию) в правой части определения. Так что instance Eq ItemSlot where is1 == is2 = item is1 == item is2 тоже подойдет. Однако способ сопоставления с образцом, вероятно, лучше. - person Antal Spector-Zabusky; 26.08.2012
comment
@Antal S-Z: Это именно то, что я изначально считал лучшим способом, но, очевидно, я отвлекся, когда писал свой ответ. Я отредактировал свой ответ, но проголосовал за ваш комментарий. Спасибо, напомнил. - person AndrewC; 27.08.2012

Сопоставление с образцом работало в примере TrafficLight, потому что вам нужно было знать, что такое конструктор (Red, Green или Yellow), чтобы сказать, равны ли они, но ваши ItemSlot равны только в том случае, если данные в item поле равно, поэтому вам нужно проверить это с помощью уравнения в правой части:

instance Eq ItemSlot where
        ItemSlot {item=i} == ItemSlot {item=j} = i == j

Это эквивалент

instance Eq ItemSlot where
        ItemSlot i _ == ItemSlot j _  =  i == j

но это больше рассчитано на будущее, потому что, если вы добавите еще одно поле и не хотите менять значение ==, вы можете оставить первую версию в покое. (Вы можете возразить, что вам следует вернуться к == при добавлении полей, но использование синтаксиса {item =, по моему опыту, приводит к более четким сообщениям об ошибках.

Самый чистый

instance Eq ItemSlot where 
        i == j  =  item i == item j

как мне напомнил Antal S-Z (спасибо).

Если вы тестируете с

eg1 = ItemSlot {item = Carrot, number = 3}
eg2 = ItemSlot {item = Onion, number = 3}
eg3 = ItemSlot {item = Onion, number = 42}
eg4 = ItemSlot {item = Carrot, number = undefined}
eg5 = ItemSlot {item = Carrot}

Вы обнаружите, что eg5 выдает вам предупреждение. Вам разрешено игнорировать поля при использовании записи, поэтому первая версия Eq выше подходит, но если вы определяете запись, Haskell хотел бы, чтобы вы предоставили все данные.

Вы можете проверить eg4 == eg1 и eg2 == eg4 и даже eg2 == eg5 без проблем - ленивая оценка означает, что она не проверяет числовое поле при проверке ==, но если вы просто наберете eg4 или eg5, она не завершится, потому что встретит неопределенные значения.

person AndrewC    schedule 26.08.2012