Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial application of functions and currying, how to make a better code instead of a lot of maps?

I am a beginner at Haskell and I am trying to grasp it.

I am having the following problem:

I have a function that gets 5 parameters, lets say

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

And I would like to apply it while only changing the variable x from 1 to 10 whereas y, w, z and a will always be the same. The implementation I achieved was the following but I think there must be a better way.

Let's say I would like to use:

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

Accordingly to this I managed to apply the function as following:

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

I think there must be a better way to apply a lot of missing parameters to partially applied functions without having to use too many maps.

like image 518
Miguelme Avatar asked Oct 10 '15 03:10

Miguelme


People also ask

Is partial application the same as currying?

Simple answer. Currying: Lets you call a function, splitting it in multiple calls, providing one argument per-call. Partial Application: Lets you call a function, splitting it in multiple calls, providing multiple arguments per-call.

What is partial application functional programming?

Partial Application: The process of applying a function to some of its arguments. The partially applied function gets returned for later use. In other words, a function that takes a function with multiple parameters and returns a function with fewer parameters.

What does partial function application in Haskell do?

Partial function application refers to calling a multiple parameter function with less than its total number of parameters. This results in a new function taking the remaining number of parameters.

What is partial application in JavaScript?

Partial application allows us to fix a function's arguments. This lets us derive new functions, with specific behavior, from other, more general functions. Currying transforms a function that accepts multiple arguments “all at once” into a series of function calls, each of which involves only one argument at a time.


6 Answers

All the suggestions so far are good. Here's another, which might seem a bit weird at first, but turns out to be quite handy in lots of other situations.

Some type-forming operators, like [], which is the operator which maps a type of elements, e.g. Int to the type of lists of those elements, [Int], have the property of being Applicative. For lists, that means there is some way, denoted by the operator, <*>, pronounced "apply", to turn lists of functions and lists of arguments into lists of results.

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

rather than your ordinary application, given by a blank space, or a $

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

The upshot is that we can do ordinary functional programming with lists of things instead of things: we sometimes call it "programming in the list idiom". The only other ingredient is that, to cope with the situation when some of our components are individual things, we need an extra gadget

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

which wraps a thing up as a list, to be compatible with <*>. That is pure moves an ordinary value into the applicative idiom.

For lists, pure just makes a singleton list and <*> produces the result of every pairwise application of one of the functions to one of the arguments. In particular

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

is a list of functions (just like map f [1..10]) which can be used with <*> again. The rest of your arguments for f are not listy, so you need to pure them.

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

For lists, this gives

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

i.e. the list of ways to make an application from the f, one of the [1..10], the 1, the 2, the 3 and the 4.

The opening pure f <*> s is so common, it's abbreviated f <$> s, so

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

is what would typically be written. If you can filter out the <$>, pure and <*> noise, it kind of looks like the application you had in mind. The extra punctuation is only necessary because Haskell can't tell the difference between a listy computation of a bunch of functions or arguments and a non-listy computation of what's intended as a single value but happens to be a list. At least, however, the components are in the order you started with, so you see more easily what's going on.

Esoterica. (1) in my (not very) private dialect of Haskell, the above would be

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

where each idiom bracket, (|f a1 a2 ... an|) represents the application of a pure function to zero or more arguments which live in the idiom. It's just a way to write

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

Idris has idiom brackets, but Haskell hasn't added them. Yet.

(2) In languages with algebraic effects, the idiom of nondeterministic computation is not the same thing (to the typechecker) as the data type of lists, although you can easily convert between the two. The program becomes

f (range 1 10) 2 3 4

where range nondeterministically chooses a value between the given lower and upper bounds. So, nondetermism is treated as a local side-effect, not a data structure, enabling operations for failure and choice. You can wrap nondeterministic computations in a handler which give meanings to those operations, and one such handler might generate the list of all solutions. That's to say, the extra notation to explain what's going on is pushed to the boundary, rather than peppered through the entire interior, like those <*> and pure.

Managing the boundaries of things rather than their interiors is one of the few good ideas our species has managed to have. But at least we can have it over and over again. It's why we farm instead of hunting. It's why we prefer static type checking to dynamic tag checking. And so on...

like image 108
pigworker Avatar answered Sep 29 '22 11:09

pigworker


Others have shown ways you can do it, but I think it's useful to show how to transform your version into something a little nicer. You wrote

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

map obeys two fundamental laws:

  1. map id = id. That is, if you map the identity function over any list, you'll get back the same list.
  2. For any f and g, map f . map g = map (f . g). That is, mapping over a list with one function and then another one is the same as mapping over it with the composition of those two functions.

The second map law is the one we want to apply here.

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]

What does ($ a) . ($ b) do? \x -> ($ a) $ ($ b) x = \x -> ($ a) $ x b = \x -> x b a. What about ($ a) . ($ b) . ($ c)? That's (\x -> x b a) . ($ c) = \y -> (\x -> x b a) $ ($ c) y = \y -> y c b a. The pattern now should be clear: ($ a) . ($ b) ... ($ y) = \z -> z y x ... c b a. So ultimately, we get

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]
like image 44
dfeuer Avatar answered Oct 02 '22 11:10

dfeuer


In addition to what the other answers say, it might be a good idea to reorder the parameters of your function, especially x is usually the parameter that you vary over like that:

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

If you make it so that the x parameter comes last, you can just write

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

This won't work in all circumstances of course, but it is good to see what parameters are more likely to vary over a series of calls and put them towards the end of the argument list and parameters that tend to stay the same towards the start. When you do this, currying and partial application will usually help you out more than they would otherwise.

like image 34
David Young Avatar answered Sep 28 '22 11:09

David Young


Assuming you don't mind variables you simply define a new function that takes x and calls f. If you don't have a function definition there (you can generally use let or where) you can use a lambda instead.

f' x = f x 1 2 3 4

Or with a lambda

\x -> f x 1 2 3 4
like image 31
Guvante Avatar answered Oct 01 '22 11:10

Guvante


Currying won't do you any good here, because the argument you want to vary (enumerate) isn't the last one. Instead, try something like this.

map (\x -> f x 1 2 3 4) [1..10]
like image 23
Kwarrtz Avatar answered Sep 28 '22 11:09

Kwarrtz


The general solution in this situation is a lambda:

\x -> f x 1 2 3 4

however, if you're seeing yourself doing this very often, with the same argument, it would make sense to move that argument to be the last argument instead:

\x -> f 1 2 3 4 x

in which case currying applies perfectly well and you can just replace the above expression with

f 1 2 3 4

so in turn you could write:

map (f 1 2 3 4) [1..10]
like image 42
Erik Kaplun Avatar answered Sep 28 '22 11:09

Erik Kaplun