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

I am new to Haskell and I am trying to understand it.

I have the following problem:

I have a function that gets 5 parameters, let's say

fxywza = x - y - w - z - a 

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

Say I would like to use:

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

In accordance with this, I managed to apply the function as follows:

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

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

+5
source share
6 answers

All suggestions are good so far. Here is another, which may seem a little strange at first, but it turns out to be very convenient in many other situations.

Some type formation operators, such as [] , which is an operator that displays the type of elements, for example. Int to the list type of these elements [Int] , have the property of being Applicative . For lists, this means that there is some way, indicated by the operator, <*> , pronounced "apply" to list function lists and argument lists in result lists.

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

not your regular application given by empty space, or $

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

The result is that we can do normal functional programming with lists of things instead of things: we sometimes call it "programming in a list idiom." The only other component is that to cope with a situation where some of our components are separate things, we need an additional gadget

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

which wraps the thing as a list compatible with <*> . This pure moves the usual meaning into the applicative idiom.

For lists, pure simply makes a singleton list, and <*> gives the result of each 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 (like map f [1..10] ), which can be used with <*> again. The rest of the arguments to f are not lists, so you need 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. a list of ways to make an application from f, one of [1..10], 1, 2, 3, and 4.

The discovery of pure f <*> s so common, it is abbreviated f <$> s , therefore

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

- this is what is usually written. If you can filter out the noises <$> , pure and <*> , this is similar to the application you had in mind. Additional punctuation is necessary only because Haskell cannot determine the difference between counting a list of functions or arguments and non-listy computing what is intended as a single value, but it turns out to be a list. However, at least the components are in the order you started, so you can more easily see what happens.

Esoterics. (1) in my (not so) private Haskell dialect , above would be

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

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

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

Idris has idiomatic brackets, but Haskell did not add them. Nonetheless.

(2) In languages ​​with algebraic effects, the idiom of non-deterministic computing is not the same thing (typechecker type) as the data type of lists, although you can easily convert between them. Program becomes

 f (range 1 10) 2 3 4 

where the range non-deterministically selects a value between the given lower and upper bounds. Thus, uncertainty is seen as a local side effect, rather than a data structure that allows operations to be made for failure and selection. You can wrap non-deterministic calculations in a handler that gives values ​​to these operations, and one of these handlers can generate a list of all solutions. This means that additional notations to explain what is happening are pushed to the border, and do not penetrate the entire interior, like those <*> and pure .

Managing the boundaries of things, not their interiors, is one of the few good ideas that our species has managed to have. But at least we can repeat it over and over. This is why we are instead of a farm instead of hunting. This is why we prefer static type checking to dynamic tag checking. And so on...

+14
source

Others have shown ways in which you can do this, but I find it useful to show how to turn your version into something more enjoyable. You wrote

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

map obeys two fundamental laws:

  • map id = id . That is, if you map the identification function over any list, you will get the same list.
  • For any f and g , map f . map g = map (f . g) map f . map g = map (f . g) . That is, a list display with one function, and then another, is the same as a map above it with the composition of these two functions.

The second law of map 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) ($ a) . ($ b) ? \x -> ($ a) $ ($ b) x = \x -> ($ a) $ xb = \x -> xba . What about ($ a) . ($ b) . ($ c) ($ a) . ($ b) . ($ c) ($ a) . ($ b) . ($ c) ? This is (\x -> xba) . ($ c) = \y -> (\x -> xba) $ ($ c) y = \y -> ycba (\x -> xba) . ($ c) = \y -> (\x -> xba) $ ($ c) y = \y -> ycba . Now the template should be clear: ($ a) . ($ b) ... ($ y) = \z -> zyx ... cba ($ a) . ($ b) ... ($ y) = \z -> zyx ... cba . So we get

 map ((\z -> z 1 2 3 4) . f) [1..10] = map (\w -> (\z -> z 1 2 3 4) (fw)) [1..10] = map (\w -> fw 1 2 3 4) [1..10] = map (\x -> ($ 4) $ ($ 3) $ ($ 2) $ ($ 1) $ fx) [1..10] 
+7
source

In addition to what the other answers say, it might be a good idea to reorder the parameters of your function, especially x usually a parameter that you change as follows:

 fywzax = x - y - w - z - a 

If you make parameter x appear last, you can simply write

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

Of course, this will not work under any circumstances, but it's good to see which parameters are more likely to change for a number of calls and put them at the end of the list of arguments and parameters, which, as a rule, remain unchanged at the beginning. When you do, currying and a partial application will usually help you more than otherwise.

+5
source

Assuming you're not against variables, you just define a new function that takes x and calls f . If you don't have a function definition (usually you can use let or where ), you can use lambda instead.

 f' x = fx 1 2 3 4 

Or using lambda

 \x -> fx 1 2 3 4 
+4
source

Currying will not bring you anything good, because the argument you want to change (list) is not the last. Instead, try something like this.

 map (\x -> fx 1 2 3 4) [1..10] 
+4
source

The general solution in this situation is lambda:

 \x -> fx 1 2 3 4 

however, if you see that you do this very often, with the same argument, it makes sense to move this argument instead of the last argument:

 \x -> f 1 2 3 4 x 

and in this case currying is applied perfectly, and you can just replace the above expression with

 f 1 2 3 4 

therefore, in turn, you can write:

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

Source: https://habr.com/ru/post/1233391/


All Articles