5

Я новичок в Haskell, и я пытаюсь понять это.Частичное применение функций и currying, как сделать лучший код вместо множества карт?

Я имею следующую проблему:

У меня есть функция, которая получает 5 параметров, позволяет сказать

f x y w z a = x - y - w - z - a 

И я хотел бы применить его в то время как только изменение переменной x от 1 до 10 тогда как y, w, z и a всегда будут одинаковыми. Реализация, которую я достиг, была следующей, но я думаю, что должен быть лучший способ.

Скажем, я хотел бы использовать:

x from 1 to 10 
y = 1 
w = 2 
z = 3 
a = 4 

Соответственно этому мне удалось применить функцию следующим образом:

map ($ 4) $ map ($ 3) $ map ($ 2) $ map ($ 1) (map f [1..10]) 

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

ответ

4

Предполагая, что вы не против переменных, вы просто определяете новую функцию, которая принимает x и вызывает f. Если у вас нет определения функции (обычно вы можете использовать let или where), вы можете использовать лямбда вместо этого.

f' x = f x 1 2 3 4 

Или с лямбда

\x -> f x 1 2 3 4 
4

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

map (\x -> f x 1 2 3 4) [1..10] 
7

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

map ($ 4) $ map ($ 3) $ map ($ 2) $ map ($ 1) (map f [1..10]) 

map подчиняется два фундаментальных закона:

  1. map id = id. То есть, если вы сопоставляете функцию идентификации над любым списком, вы получите тот же список.
  2. Для любых f и g, map f . map g = map (f . g). То есть отображение над списком с одной функцией, а затем другое - это сопоставление с ним с помощью композиции этих двух функций.

Вторым законом map является тот, который мы хотим применить здесь.

map ($ 4) $ map ($ 3) $ map ($ 2) $ map ($ 1) (map f [1..10]) 
= 
map ($ 4) . map ($ 3) . map ($ 2) . map ($ 1) . map f $ [1..10] 
= 
map (($ 4) . ($ 3) . ($ 2) . ($ 1) . f) [1..10] 

Что делает ($ a) . ($ b)? \x -> ($ a) $ ($ b) x = \x -> ($ a) $ x b = \x -> x b a. Как насчет ($ a) . ($ b) . ($ c)? Это (\x -> x b a) . ($ c) = \y -> (\x -> x b a) $ ($ c) y = \y -> y c b a. Теперь шаблон должен быть ясным: ($ a) . ($ b) ... ($ y) = \z -> z y x ... c b a.Поэтому в конечном итоге, мы получаем

map ((\z -> z 1 2 3 4) . f) [1..10] 
= 
map (\w -> (\z -> z 1 2 3 4) (f w)) [1..10] 
= 
map (\w -> f w 1 2 3 4) [1..10] 
= 
map (\x -> ($ 4) $ ($ 3) $ ($ 2) $ ($ 1) $ f x) [1..10] 
5

В дополнение к тому, что говорят другие ответы, это может быть хорошей идеей, чтобы изменить порядок параметров вашей функции, особенно x, как правило, параметр, который вы меняете над так:

f y w z a x = x - y - w - z - a 

Если вы сделаете так, что параметр x приходит последним, вы можете просто написать

map (f 1 2 3 4) [1..10] 

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

14

Все предложения до сих пор хороши. Вот еще один, который может показаться немного странным вначале, но оказывается весьма удобным во многих других ситуациях.

Некоторые операторы формирования типа, такие как [], который является оператором, который отображает тип элементов, например. Int к типу списков этих элементов, [Int], Иметь собственность быть Applicative. Для списков это означает, что есть какой-то способ, обозначенный оператором, <*>, произнесенный «применить», чтобы включить списки функций и содержит аргументов в результатов.

(<*>) :: [s -> t] -> [s] -> [t] -- one instance of the general type of <*> 

вместо вашего обычного приложения, учитывая пробел или $

($) :: (s -> t) -> s -> t 

Получается, что мы можем сделать обычное функциональное программирование со списками вещей вместо вещей: мы иногда называем это "программирование в списке idiom". Единственный другой ингредиент состоит в том, чтобы справиться с ситуацией, когда некоторые из наших компонентов индивидуальных вещей, нам нужен дополнительный гаджет

pure :: x -> [x] -- again, one instance of the general scheme 

который оборачивает вещь в качестве списка, чтобы быть совместимыми с <*>. То есть pure перемещает обычное значение в аппликативную идиому.

Для списков pure просто делает одиночный список, а <*> производит результат каждого попарного применения одной из функций к одному из аргументов. В частности

pure f <*> [1..10] :: [Int -> Int -> Int -> Int -> Int] 

список функций (так же, как map f [1..10]), которые могут быть использованы с <*> снова. Остальные аргументы за f не перечислены, поэтому вам нужно указать pure.

pure f <*> [1..10] <*> pure 1 <*> pure 2 <*> pure 3 <*> pure 4 

Для списков, это дает

[f] <*> [1..10] <*> [1] <*> [2] <*> [3] <*> [4] 

т.е. список способов, чтобы сделать приложение из F, один из [1..10], то 1, 2, 3 и 4.

Открытие pure f <*> s настолько распространена, что это аббревиатура f <$> s, так

f <$> [1..10] <*> [1] <*> [2] <*> [3] <*> [4] 

является то, что, как правило, записывается. Если вы можете отфильтровать шумы <$>, pure и <*>, это похоже на приложение, которое вы имели в виду. Дополнительная пунктуация необходима только потому, что Haskell не может определить разницу между вычислением списка кучи функций или аргументов и неличным вычислением того, что предназначено как одно значение, но, как оказалось, является списком. Однако, по крайней мере, компоненты находятся в том порядке, в котором вы начали, поэтому вы более легко видите, что происходит.

Esoterica. (1) в моей (не очень) private dialect из Haskell, выше будет

(|f [1..10] (|1|) (|2|) (|3|) (|4|)|) 

где каждый идиома кронштейн, (|f a1 a2 ... an|) представляет собой применение чистой функции к нулю или более аргументов, которые живут в идиомы , Это просто способ, чтобы написать

pure f <*> a1 <*> a2 ... <*> an 

Идрис идиома скобки, но Haskell не добавил их. Все же.

(2) В языках с алгебраическими эффектами идиома недетерминированных вычислений - это не одно и то же (тип typechecker) как тип данных списков, хотя вы можете легко преобразовать между ними. Программа превращается в

f (range 1 10) 2 3 4 

где диапазон недетерминированно выбирает значение между данными нижними и верхними границами. Таким образом, неопределенность рассматривается как побочный эффект , а не структура данных, позволяющая выполнять операции для отказа и выбора. Вы можете обернуть недетерминированные вычисления в обработчике , который дает значения этим операциям, и один из таких обработчиков может генерировать список всех решений. То есть, дополнительные обозначения, объясняющие, что происходит, выталкиваются на границу, а не проникают через весь интерьер, например, те <*> и pure.

Управление границами вещей, а не их интерьерами является одной из немногих хороших идей, которые наш вид сумел иметь. Но, по крайней мере, мы можем повторять это снова и снова. Поэтому мы вместо фермы вместо охоты. Именно поэтому мы предпочитаем проверку статического типа на динамическую проверку тегов. И так далее...

+3

один из лучших аппликативных учебники я видел! –

+0

На самом деле не получается, как недетерминизм подходит в аппликациях – pedrofurla

+1

Я еще не понимаю, какие скобки идиомы добавляют в сторону больше синтаксиса для изучения. Вы, кажется, по-разному относитесь к ним. Могли бы вы объяснить? – dfeuer

1

Общее решение в этой ситуации является лямбда:

\x -> f x 1 2 3 4 

Однако, если вы видите себя делать это очень часто, с теми же аргументами, это имело бы смысл, чтобы переместить этот аргумент, чтобы быть последний аргумент вместо:

\x -> f 1 2 3 4 x 

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

f 1 2 3 4 

так в свою очередь, можно было бы написать:

map (f 1 2 3 4) [1..10] 
+1

Извините, но '($ 1 2 3 4)' does * not * work. '$' обрабатывает только один аргумент. –

+0

oh oops, очевидно ... –

+0

'for' работает только для аппликативных действий. Вам нужно будет придерживаться 'Identity' и' runIdentity' и все, что вздор. – dfeuer