Мне нравится ответ Sjoerd Visscher, но расширения - особенно IncoherentInstances
, используемые в этом случае, чтобы сделать возможным частичное применение - могут быть немного пугающими. Вот решение, которое не требует никаких расширений.
Сначала мы определяем тип данных функций, которые знают, что делать с любым количеством аргументов. Вы должны прочитать a
здесь как «тип аргумента» и b
как «тип возврата».
data ListF a b = Cons b (ListF a (a -> b))
Тогда мы можем написать некоторые (Haskell) функции, которые изменяют эти (variadic) функции. Я использую суффикс F
для любых функций, которые входят в Prelude.
headF :: ListF a b -> b
headF (Cons b _) = b
mapF :: (b -> c) -> ListF a b -> ListF a c
mapF f (Cons v fs) = Cons (f v) (mapF (f.) fs)
partialApply :: ListF a b -> [a] -> ListF a b
partialApply fs [] = fs
partialApply (Cons f fs) (x:xs) = partialApply (mapF ($x) fs) xs
apply :: ListF a b -> [a] -> b
apply f xs = headF (partialApply f xs)
Например, функцию sum
можно рассматривать как переменную функцию:
sumF :: Num a => ListF a a
sumF = Cons 0 (mapF (+) sumF)
sumExample = apply sumF [3, 4, 5]
Однако мы также хотим иметь возможность работать с обычными функциями, которые не обязательно знают, что делать с любым количеством аргументов. Так что делать? Ну, как Лисп, мы можем генерировать исключение во время выполнения. Ниже мы будем использовать f
в качестве простого примера невариантной функции.
f True True True = 32
f True True False = 67
f _ _ _ = 9
tooMany = error "too many arguments"
tooFew = error "too few arguments"
lift0 v = Cons v tooMany
lift1 f = Cons tooFew (lift0 f)
lift2 f = Cons tooFew (lift1 f)
lift3 f = Cons tooFew (lift2 f)
fF1 = lift3 f
fExample1 = apply fF1 [True, True, True]
fExample2 = apply fF1 [True, False]
fExample3 = apply (partialApply fF1 [True, False]) [False]
Конечно, если вам не нравится шаблон определения lift0
, lift1
, lift2
, lift3
и т. Д. По отдельности, то вам нужно включить некоторые расширения. Но без них вы можете продвинуться далеко!
Вот как вы можете обобщить одну lift
функцию. Сначала мы определим несколько стандартных чисел уровня типа:
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, TypeFamilies, UndecidableInstances #-}
data Z = Z
newtype S n = S n
Затем введите класс типов для подъема. Вы должны прочитать тип I n a b
как "n
копии a
в качестве аргументов, а затем тип возврата b
".
class Lift n a b where
type I n a b :: *
lift :: n -> I n a b -> ListF a b
instance Lift Z a b where
type I Z a b = b
lift _ b = Cons b tooMany
instance (Lift n a (a -> b), I n a (a -> b) ~ (a -> I n a b)) => Lift (S n) a b where
type I (S n) a b = a -> I n a b
lift (S n) f = Cons tooFew (lift n f)
А вот примеры использования f
, переписанного с использованием обобщенного лифта:
fF2 = lift (S (S (S Z))) f
fExample4 = apply fF2 [True, True, True]
fExample5 = apply fF2 [True, False]
fExample6 = apply (partialApply fF2 [True, False]) [False]