Подтвердить что ты не робот

Полиморфизм в функциях высшего порядка?

У меня есть тип алгебраических данных с некоторыми конструкторами, которые имеют сопоставимые значения, и некоторые конструкторы, которые этого не делают. Я написал несколько функций сравнения, которые работают как стандартные операторы (==) и (/=), но возвращают 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.

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

4b9b3361

Ответ 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 вне этой функции; тип значения вышел из сферы действия, что затрудняет сделать что-либо разумное (как вы, вероятно, можете себе представить).