Полиморфизм в функциях высшего порядка? - PullRequest
16 голосов
/ 15 августа 2011

У меня есть алгебраический тип данных с некоторыми конструкторами, которые содержат сопоставимые значения, и некоторыми конструкторами, которые не имеют. Я написал несколько функций сравнения, которые работают как стандартные операторы (==) и (/=), но возвращают Nothing для сравнений, которые не имеют смысла:

data Variant = IntValue Int
             | FloatValue Float
             | NoValue

equal :: Variant -> Variant -> Maybe Bool
equal (IntValue a) (IntValue b) = Just (a == b)
equal (FloatValue a) (FloatValue b) = Just (a == b)
equal _ _ = Nothing

unequal :: Variant -> Variant -> Maybe Bool
unequal (IntValue a) (IntValue b) = Just (a /= b)
unequal (FloatValue a) (FloatValue b) = Just (a /= b)
unequal _ _ = Nothing

Это работает, но повторение громоздко - тем более, что у меня на самом деле больше конструкторов Variant и больше функций сравнения.

Я думал, что смогу выделить повторение в вспомогательную функцию, параметризованную в функции сравнения:

helper :: (Eq a) => (a -> a -> Bool) -> Variant -> Variant -> Maybe Bool
helper f (IntValue a) (IntValue b) = Just (f a b)
helper f (FloatValue a) (FloatValue b) = Just (f a b)
helper _ _ _ = Nothing

equal' :: Variant -> Variant -> Maybe Bool
equal' = helper (==)

unequal' :: Variant -> Variant -> Maybe Bool
unequal' = helper (/=)

но это не работает, потому что переменная типа a, по-видимому, не может связываться одновременно с Int и Float в определении helper; GHC связывает его с Float, а затем жалуется на несоответствие типов в строке, которая обрабатывает IntValue.

Функция, подобная (==), полиморфна при непосредственном использовании; Есть ли способ передать его в другую функцию и оставить его полиморфным?

1 Ответ

15 голосов
/ 15 августа 2011

Да, это возможно, но только с языковыми расширениями :

{-# LANGUAGE Rank2Types #-}

helper :: (forall a. (Eq a) => (a -> a -> Bool))
       -> Variant -> Variant -> Maybe Bool
helper f (IntValue a) (IntValue b) = Just (f a b)
helper f (FloatValue a) (FloatValue b) = Just (f a b)
helper _ _ _ = Nothing

forall a. делает то, на что это похоже; a определяется количественно в скобках и выходит за их пределы. Это означает, что аргумент f должен быть полиморфным по всем типам a, которые являются экземплярами Eq, и это именно то, что вам нужно.

Расширение здесь называется «рангом 2», потому что оно допускает обычный стиль полиморфизма во внешней области видимости плюс полиморфные аргументы, как в примере здесь. Чтобы вложить вещи дальше, вам нужно расширение RankNTypes, которое довольно информативно.

Кроме того, в отношении полиморфных типов более высокого ранга - имейте в виду, что forall - это то, что фактически связывает переменную с типом; на самом деле вы можете думать о них как о лямбдах. Когда вы применяете такую ​​функцию к чему-то с конкретным типом, тип аргумента неявно связывается forall для этого использования. Это происходит, например, если вы пытаетесь использовать значение, тип которого был связан внутренним forall вне этой функции; тип значения вышел из области видимости, что затрудняет выполнение чего-либо разумного (как вы, вероятно, можете себе представить).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...