5

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.

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
Miguelme
  • 609
  • 1
  • 6
  • 17

6 Answers6

14

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...

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
pigworker
  • 43,025
  • 18
  • 121
  • 214
  • 3
    one of the best applicative tutorials I've seen! – Erik Kaplun Oct 10 '15 at 10:16
  • Don't really get how nondeterminism fits in applicatives – pedrofurla Oct 10 '15 at 14:41
  • 1
    I don't yet understand what idiom brackets add aside from more syntax to learn. You seem to feel rather differently about them. Could you explain? – dfeuer Oct 10 '15 at 16:21
  • @pedrofuria: nondeterminism is simply when you have many outputs from the same logic for potentially many inputs, whereas it all seems to happen in a single expression. as is exactly the case with the list applicative/monad, also known as the indeterminism monad (or smth). – Erik Kaplun Oct 10 '15 at 16:35
  • @dfeuer I think this is explained in the last bit, about boundaries. I can imagine this as writing `{IO| ..... {List| .... } ..... {Maybe| ....} .... }` or something like that. (?) – Will Ness Dec 30 '15 at 01:10
7

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]
Will Ness
  • 70,110
  • 9
  • 98
  • 181
dfeuer
  • 48,079
  • 5
  • 63
  • 167
5

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.

David Young
  • 10,713
  • 2
  • 33
  • 47
4

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
Guvante
  • 18,775
  • 1
  • 33
  • 64
4

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]
Kwarrtz
  • 2,693
  • 15
  • 24
1

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]
dfeuer
  • 48,079
  • 5
  • 63
  • 167
Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111