Я собирался отредактировать другое сообщение, но это достаточно большое для себя.
Вот один из способов сделать это с помощью «магии типа», но мне кажется, что он несколько субоптимален, так как для него требуется функция подъема, которая специфична для функций определенного количества аргументов (более подробно).
Давайте начнем с определения рекурсивного типа данных
data RecT a = RecR a
| RecC (a -> RecT a)
Так переменные типа RECT могут быть либо просто завернуты результат (Recr) или они могут быть по-прежнему рекурсии (RecC).
Теперь, как мы берем что-то и набрасываем его на тип RecT a?
Значения легко:
valRecT x = RecR x
Recr х, очевидно, типа Rect а.
Как насчет функции, которая принимает один аргумент, например id?
idRecT x = RecC $ \x -> RecR x
RecC обертывает функцию, которая принимает переменную и возвращает тип RecT a. Выражение
\x -> RecR x
является такой функцией, поскольку, как мы видели ранее, RecR x имеет тип RecT a.
В целом, любая функция одного аргумента может быть снята:
lift1RecT :: (a -> a) -> RecT a
lift1RecT fn = RecC $ \a -> RecR $ fn a
Мы можем обобщить это, неоднократно оберточные более глубоко вложенные вызовы функций внутри RecC:
lift2RecT :: (a -> a -> a) -> RecT a
lift2RecT fn = RecC $ \b -> RecC $ \a -> RecR $ fn b a
lift3RecT :: (a -> a -> a -> a) -> RecT a
lift3RecT fn = RecC $ \c -> RecC $ \b -> RecC $ \a -> RecR $ fn c b a
КИ, так что мы Выполняем всю эту работу, чтобы превратить функцию произвольного числа аргументов в один тип, RecT a. Как мы это используем?
Мы можем легко записать на один уровень применения функции:
reduceRecT :: RecT a -> a -> RecT a
reduceRecT (RecC fn) = fn
reduceRecT _ = undefined
Другими словами, reduceRecT принимает аргумент типа Rect а и другого типа а и возвращает новый Rect, который был один уровень снижается ,
Мы можем также раскатать готовое вычисление внутри Rect в результате:
unrollRecT :: RecT a -> a
unrollRecT (RecR fn) = fn
unrollRecT _ = undefined
Теперь мы готовы применить список аргументов функции!
lApply :: [a] -> RecT a -> a
lApply [] fn = unrollRecT fn
lApply (l:ls) fn = lApply ls $ (reduceRecT fn) l
Давайте рассмотрим базовый вариант первый: если мы закончили с вычислением, мы просто разворачивать результат и вернуть его.В рекурсивном случае мы уменьшаем список аргументов на единицу, а затем преобразуем fn, применяя заголовок списка к уменьшенному fn, в результате получим новый RecT a.
Давайте дадим это попробовать:
lApply [2,5] $ lift2RecT (**)
> 32.0
Таким образом, преимущества и недостатки такого подхода? Ну, версия Template Haskell может выполнять приложение с частичным списком; это не относится к решению изорекурсивного типа, представленному здесь (хотя мы могли бы в принципе исправить это с некоторым уродством). У решения типа также есть недостаток, связанный с тем, что с ним связано гораздо более шаблонный код: нам нужен списокNRecT для всех N, которые мы хотим использовать. Наконец, гораздо проще обобщить это на аналогичное решение кортежа, если мы хотим использовать функции смешанных переменных типов.
Конечно, еще одна интересная возможность заключается в улучшении краткости с помощью шаблона Haskell для генерации функций listNRecT; это устраняет некоторые шаблоны, но в некотором смысле покупает недостатки обеих реализаций.
Быстро посмотрите на поливариадические функции, но это может взорвать ваш ум: [http://stackoverflow.com/questions/3467279/how-to-create-a-polyvariadic-haskell-function] – fuz