Вы правильно определили, что вам нужно универсальное количественное определение .Фактически, у вас уже есть универсальное количественное определение - ваша подпись, как и любая полиморфная подпись, в основном является сокращением для
{-# LANGUAGE ExplicitForall, UnicodeSyntax #-}
calculate :: ∀ a . Num a => (a -> a -> a) -> Val -> Val -> Val
, означающим: всякий раз, когда кто-то хочет использовать эту функцию, он выбирает какой-либо тип для вставки вa
аванс.Например, они могут выбрать Int
, тогда функция станет специализированной для
calculate :: (Int -> Int -> Int) -> Val -> Val -> Val
, и тогда она будет использоваться во время выполнения.
Но это бесполезно для вас, потому что вынеобходимо использовать эту функцию для различных числовых типов.Ни одна специализация не охватит их всех.
Решение: отложить выбор типа.Это достигается путем помещения универсального квантора ∀
(вы также можете написать его forall
) внутри функции сигнатуры с функцией комбинатора:
{-# LANGUAGE Rank2Types #-}
calculate :: (∀ a . Num a => a -> a -> a) -> Val -> Val -> Val
, которая будет проверять тип.Для этого требуется расширение -XRank2Types
, потому что это довольно сложный зверь: теперь вы не можете просто представить полиморфную функцию как семейство специализаций с конкретными мономорфными типами, но вместо этого функция должна быть готова к реализации, во время выполнения , предоставляемая функция с любыми типами, которые встречаются в структуре данных.
Т.е. ей необходимо передать дополнительный аргумент функции: «словарь», содержащий класс Num
методы.Базовая реализация, сгенерированная GHC, выглядит примерно так:
data NumDict a = NumDict {
addition :: a -> a -> a
, subtraction :: a -> a -> a
, multiplication :: a -> a -> a
, abs :: a -> a
...
}
calculate' :: (∀ a . NumDict a -> a -> a -> a) -> Val -> Val -> Val
calculate' f (I i1) (I i2) = I (f ndict i1 i2)
where ndict = NumDict ((+) :: Integer -> Integer -> Integer)
((-) :: Integer -> Integer -> Integer)
...