2010-09-23 4 views
5

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

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) 

Возможно ли это? Как я ошибаюсь?

+0

Быстро посмотрите на поливариадические функции, но это может взорвать ваш ум: [http://stackoverflow.com/questions/3467279/how-to-create-a-polyvariadic-haskell-function] – fuz

ответ

3

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

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" 

Вам потребуется отдельный Simplify функция для каждой арности, проблема, которая наиболее легко фиксируется Template Haskell.

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

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

  2. Шаблон Haskell.

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

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

Редактировать: Оказалось, что в Hackage есть somepackages, которые связаны с этой проблемой. Использование «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 

Но «итерация» и «перечислитель», скорее всего, переполнены.

+0

Спасибо. Это помогает прояснить многое. – Ishpeck

+0

'В первом случае для результата нужны два аргумента' - если это не будет' нужен один аргумент' - '4 3 2' нуждается в' 1'? –

1

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

7

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

Прежде всего, ваша функция «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

Edit:.

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

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

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

+0

Это не будет подразумевают динамическую типизацию; это подразумевало бы * зависимую типизацию *. –

2

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

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 

Это означает, что М.Ф. не может взять функцию произвольного «арностью», вы должны решить для одного. По той же причине вы не можете преобразовать произвольный список в кортеж: вы не можете сказать, сколько элементов будет храниться в кортеже, но система типа должна знать.

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

[править]

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

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

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

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

То, что я хотел бы сделать, это волшебная функция такая, что, например

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

таким образом, что Могу сказать:

listApply [1,2,3] someFunc 

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

Я какое-то время взломал решение типа; это кажется возможным, но я не совсем понял, как это работает, прежде чем решиться попробовать Template 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 |] 

(Не забудьте использовать язык прагму или командной строки переключатель -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, который, кстати, вы также можете создать с помощью Template Haskell. Это остается как упражнение для читателя :)

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

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

1

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

Вот один из способов сделать это с помощью «магии типа», но мне кажется, что он несколько субоптимален, так как для него требуется функция подъема, которая специфична для функций определенного количества аргументов (более подробно).

Давайте начнем с определения рекурсивного типа данных

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

Смежные вопросы