Передача элементов списка в качестве параметров функции curried


Здесь все еще новичок Хаскелл. Я знаю ровно столько, чтобы попасть в беду из-за неверных предположений. Если у меня есть следующая функция...

quadsum w x y z = w+x+y+z

Мне нужна функция, которая может принимать список, использовать каждый элемент в качестве параметра в указанной функции, например quadsum, и возвращать функцию карри для последующего использования.

Я пробовал кое-что для эффекта...

magicalFunctionMaker f [] = (f)
magicalFunctionMaker f (x:xs) = magicalFunctionMaker (f x) xs

С надеждой, что смогу это сделать...

magicalFunctionMaker (quadsum) [4,3,2]

Получение функции Карри любить...:

(((quadsum 4) 3) 2)

Или, в качестве альтернативы, позвоните:

magicalFunctionMaker (quadsum) [4,3,2,1]

В результате чего...

((((quadsum 4) 3) 2) 1)

Возможно ли это? Насколько я заблуждаюсь?

6 6

6 ответов:

Ответ Пола Джонсона в значительной степени покрывает это. Просто сделай

quadsum 4 3 2

И результатом будет функция, которую вы хотите, с типом Integer -> Integer.

Но иногда этого недостаточно. Иногда вы получаете списки чисел, вы не знаете, какой длины списки, и вам нужно применить элементы к вашей функции. Это немного сложнее. Вы не можете сделать:
magicalFunction2 f [] = f
magicalFunction2 f (x1:x2:xs) = f x1 x2
Потому что результаты имеют разные типы. В первом случае результат нуждается в двух аргументах, а во втором-в двух аргументах. полностью прикладная функция, так что больше никакие аргументы не допускаются. В этом случае лучше всего удерживать список и исходную функцию до тех пор, пока не появится достаточное количество аргументов:
type PAPFunc f a result = Either (f, [a]) result

magicfunc f xs = Left (f,xs)

apply (Left (f,xs)) ys = Left (f,xs++ys)
apply p _              = p

simp2 :: PAPFunc (a->a->b) a b -> PAPFunc (a->a->b) a b
simp2 (Left (f,(x1:x2:xs))) = Right (f x1 x2)
simp2 p = p

Теперь вы можете сделать:

Main> let j = magicfunc (+) []
Main> let m = apply j [1]
Main> let n = apply m [2,3]

Main> either (const "unfinished") show $ simp2 m
"unfinished"
Main> either (const "unfinished") show $ simp2 n
"3"

Вам понадобится отдельная функция упрощения для каждого arity, проблема, которую легче всего исправить шаблоном Haskell.

Использование списков аргументов (в отличие от аргумента списков), как правило, очень неудобно в Haskell, потому что все множественные результаты имеют различные типы, и существует очень мало поддержки для коллекций с переменным числом разнотипных аргументов. Я видел три общие категории решений:

  1. Явный код для каждого случая отдельно (быстро становится много работа).

  2. Шаблон Хаскелл.

  3. Тип системы хакерства.

Мой ответ в основном касается попыток сделать 1 менее болезненным. 2 и 3 не для слабонервных.

Edit: It оказывается, что есть некоторые пакеты на Хакаже, связанные с этой проблемой. Использование "iteratee":

import qualified Data.Iteratee as It
import Control.Applicative

magic4 f = f <$> It.head <*> It.head <*> It.head <*> It.head

liftedQuadsum = magic4 quadsum
-- liftedQuadsum is an iteratee, which is essentially an accumulating function
-- for a list of data

Main> p <- It.enumChunk (It.Chunk [1]) liftedQuadsum
Main> It.run p
*** Exception: EofException
Main> q <- It.enumChunk (It.Chunk [2,3,4]) p
Main> It.run q
10

Но "iteratee "и" enumerator", скорее всего, перебор.

Я думаю, что вы неправильно понимаете систему типа Хаскелла.

Во-первых, ваша функция" quadsum " уже каррирована. Вы можете написать "quadsum 4 3" и получить обратно функцию, которая ожидает 2 и 1 в качестве дополнительных аргументов. Когда вы пишете "quadsum 4 3 2 1", это эквивалентно "(((quadsum 4) 3) 2) 1)".

В Haskell список целых чисел имеет другой тип, чем целое число или кортеж, например "(4,3,2,1)". Учитывая это, довольно трудно понять, что вы пытаетесь сделать делать. Что должно произойти, если вы это напишете?

magicalFunctionMaker quadsum [5,4,3,2,1]

Ваш " magicalFunctionMaker "выглядит скорее как" foldl", за исключением того, что функция, которую вы даете foldl, принимает только два аргумента. Поэтому вы можете написать:

mySum = foldl (+) 0

Возвращает функцию, которая берет список и суммирует элементы.

(Кстати, как только вы это сделаете, узнайте о разнице между foldl и foldr.

Правка:

Перечитав свой вопрос, я думаю, что вы пытаетесь получить:

magicalFunctionMaker quadSum [4,3,2,1] :: Integer
magicalFunctionMaker quadSum [4,3,2] :: Integer -> Integer

Это невозможно, поскольку тип magicalFunctionMaker будет зависеть от длины аргумента списка, что подразумевает динамическую типизацию. Как кто-то сказал, поливариадные функции могут сделать что-то похожее на это (хотя и не с аргументом списка), но для этого требуется довольно много миллилегов типа hackery.

Я столкнулся с той же проблемой: у меня есть функция, подобная

someFunc :: Int -> Int -> Int -> Int

Что я хотел бы сделать, так это сделать магическую функцию такой, что, например

listApply :: [Int] -> (Int -> Int -> Int -> Int) -> Int

Такое, что я могу сказать

listApply [1,2,3] someFunc

, инстинктивно, кажется, и ответ Джон соглашается, что это должно быть возможным, чтобы сделать какую-то систему типа магии для того, чтобы сделать это. Существуют решения подобных проблем, включающие создание явно изорекурсивных типов данных с кучей явных свертываний и развертываний (см., например, Главу 20 типов и языков программирования, или 4-й пост в этом потоке).

Я взломал решение типа на некоторое время; это кажется возможным, но я не совсем получил его работу, прежде чем решил попробовать шаблон Haskell, и там все намного дружелюбнее.

{-# LANGUAGE TemplateHaskell #-}    

import Language.Haskell.TH
import Language.Haskell.TH.Syntax

lApply :: [Int] -> String -> ExpQ
lApply    []  fn = return $ VarE (mkName fn)
lApply (l:ls) fn = [| $(lApply ls fn) l |]

(Не забудьте использовать языковую директиву pragma или переключатель командной строки-XTemplateHaskell.)

Чтобы использовать это, вы называете lApply внутри соединения так:

> $(lApply [1,2] "+")
3

Обратите внимание, что я должен использовать строку содержит имя функции, которую я хочу вызвать: я не могу поднять функцию непосредственно в ExpQ, но я могу ссылаться на ее глобальную привязку. Я понимаю, как это может раздражать. Кроме того, из-за способа, которым мы проходим по списку, аргументы должны быть представлены в обратном порядке в списке.

Есть несколько других морщин: Чтобы обобщить это на другие типы данных, эти типы должны иметь соответствующие экземпляры в классе Lift. Например, у Double нет экземпляра, но вы можете легко сделайте один:

instance Lift Double where
        lift x = return $ LitE (RationalL (toRational x))

Тип данных Lit не имеет конструктора DoubleL, но RationalL можно использовать вместо него, так как он будет соединяться с общим членом дробного класса.

Если вы хотите использовать это с функциями, которые принимают смесь типов в качестве аргументов, вы не сможете передать список, так как списки не могут быть смешанных типов. Вы можете использовать для этого кортежи, что, честно говоря, не намного сложнее, используя шаблон Haskell. В этом случае вы создадите функцию, которая генерирует AST функции, которая берет кортеж с соответствующими типами внутри и сопоставляет его вызову функции, который вы хотите. Кроме того, вы можете обернуть типы аргументов внутри соответствующим образом созданного ADT, который, кстати, вы также можете создать с помощью шаблона Haskell. Это оставлено в качестве упражнения для читателя:)

Наконец, применяются все ограничения стандартного шаблона Haskell. Например, вы не можете вызвать эту функцию из модуля, в котором она определена, из-за стадии GHC ограничение.

Template Haskell-забавная и интересная штука, но, если быть полностью честным, решение iso-рекурсивного типа данных, вероятно, немного более высокопроизводительно и, очевидно, не требует дополнительного использования TH. Я вернусь и опубликую продолжение, если/когда я получу эту работу :)

Вы даже не можете перечислить случаи разной длины "вручную":

mf f [] = f
mf f [x] = f x
mf f [x,y] = f x y

--Occurs check: cannot construct the infinite type: t = t1 -> t
--Probable cause: `f' is applied to too many arguments
--In the expression: f x
--In the definition of `mf': mf f [x] = f x
Это означает, что mf не может взять функцию произвольной "арности", вы должны решить за нее. По той же причине вы не можете преобразовать произвольный список в кортеж: вы не можете сказать, сколько элементов будет содержать кортеж, но система типов должна знать.

Решение может быть найдено путем ограничения f рекурсивным типом a = a -> a с помощью "forall" (см. http://www2.tcs.ifi.lmu.de/~abel/fomega/hm.html ). Однако я не смог заставить его работать (кажется, я должен сказать Leksah, чтобы использовать флаг-XRankNTypes где-то), и это ограничение на f сделало бы вашу функцию довольно бесполезной.

[edit]

Думая о том, что ближе всего к тому, что вы хотите, вероятно, какая-то функция сокращения. Как указал пол, это похоже на foldl, foldr... (но версия ниже без "дополнительного аргумента" и с однородным типом по сравнению с foldl. Обратите внимание на отсутствие базового варианта для пустых списков)

mf :: (a → a → a) -> [a] -> a
mf f [x] = x
mf f (x:y:xs) = mf f ((f x y) : xs)

Не собираюсь работать, я думаю. (((quadsum 4) 3) 2) имеет другой тип Intger -> Integer, например, чем ((((quadsum 4) 3) 2) 1), который имеет тип Integer.

Я собирался отредактировать свой другой пост, но этот достаточно большой для своего собственного.

Вот один из способов сделать это с помощью "магии типов", но мне кажется, что это несколько неоптимально, поскольку для этого требуется функция подъема, которая специфична для функций определенного числа аргументов (подробнее ниже). Давайте начнем с определения рекурсивного типа данных
data RecT a = RecR a
            | RecC (a -> RecT a)

, так что переменные типа прямоугольник могут быть просто завернутые результата (места) или их может быть продолжение рекурсии (РЭЦ).

Теперь, как мы возьмем что-то и бросим его в тип RecT a?

Значения просты:

valRecT x = RecR x

RecR x, очевидно, относится к типу RecT a.

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

Мы также можем развернуть законченное вычисление внутри прямой кишки в результат:

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
Итак, преимущества и недостатки этого подхода? Ну, версия шаблона Haskell может делать частичное приложение списка; это не верно для решения типа isorecursive, как представлено здесь (хотя мы могли бы в принципе исправить это с некоторым уродством). Решение типа также имеет недостаток в том, что с ним связан гораздо более шаблонный код: нам нужен listNRecT для всех N, которые мы хотим использовать. Наконец, гораздо труднее обобщить это на аналогичное решение кортежа, если мы хотим притереться к функциям смешанных типов переменных.

Конечно, еще одна интересная возможность заключается в повышении краткости с помощью шаблона Haskell для генерации функций listNRecT; это устраняет некоторые шаблонные, но в некотором смысле покупает недостатки обеих реализаций.