Полиморфизм в функциях высшего порядка?
У меня есть тип алгебраических данных с некоторыми конструкторами, которые имеют сопоставимые значения, и некоторые конструкторы, которые этого не делают. Я написал несколько функций сравнения, которые работают как стандартные операторы (==)
и (/=)
, но возвращают 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
Да, это возможно, но только с языковыми расширениями:
{-# 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
, которые именно вы хотите.
Расширение здесь называется "rank 2", потому что оно допускает регулярный стиль полиморфизма в самой внешней области, а также полиморфные аргументы, как в примере здесь. Чтобы еще больше вложить информацию, вам нужно расширение RankNTypes
, которое довольно самоописательно.
В стороне, в отношении полиморфных типов более высокого ранга - имейте в виду, что forall
- это то, что на самом деле привязывает переменную к типу; вы можете думать о них, как о том, как вести себя как лямбда. Когда вы применяете такую функцию к чему-то конкретному типу, тип аргумента неявно привязан к forall
для этого использования. Это возникает, например, если вы пытаетесь использовать значение, тип которого был связан внутренним forall
вне этой функции; тип значения вышел из сферы действия, что затрудняет сделать что-либо разумное (как вы, вероятно, можете себе представить).