Partial application in Haskell with multiple argum

2020-07-22 04:28发布

问题:

Given some function f(x1,x2,x3,..,xN) it is often useful to apply it partially in several places. For example, for N=3 we could define g(x)=f(1,x,3). However, the standard partial application in Haskell does not work this way and only allows us to partially apply a function by fixing its first arguments (because all functions actually only take one argument). Is there any simple way to do something like this:

g = f _ 2 _
g 1 3

with output the value of f 1 2 3? Of course we could do a lambda-function

g=(\x1 x3 -> f x1 2 x3)

but I find this quite unreadable. For example, in Mathematica it works like this, which I find quite nice:

g=f[#1,2,#2]&
g[1,3]

with output f[1,2,3].

Edit: Maybe I should say somehting more about the motivation. I would like to use such partially applied functions in point-style compositions, i.e., in expressions like this:

h = g. f _ 2 . k

to get h 3 = g(f(k(3),2)).

回答1:

You could read this question on how to change the order of arguments, then use partial application, but really the cleanest and clearest way of doing that currently in Haskell is just directly:

g x y = f x 2 y


回答2:

No, the simplest way is to define a lambda. You can probably try and play with flip, but I doubt it would be cleaner and simpler than a lambda. Especially for longer list of arguments.



回答3:

The simplest (and canonical) way is to define a lambda. It is much more readable if you use meaningful argument names where possible

getCurrencyData :: Date -> Date -> Currency -> IO CurrencyData
getCurrencyData fromDate toDate ccy = {- implementation goes here -}

you can define your new function with lambda syntax

getGBPData = \from to -> getCurrencyData from to GBP

or without it

getGBPData from to = getCurrencyData from to GBP

or you can use combinators, but I think this is quite ugly

getGBPData = \from to -> getCurrencyData from to GBP
           = \from to -> flip (getCurrencyData from) GBP to
           = \from    -> flip (getCurrencyData from) GBP
           = \from    -> (flip . getCurrencyData) from GBP
           = \from    -> flip (flip . getCurrencyData) GBP from
           =             flip (flip . getCurrencyData) GBP


回答4:

There is no general way to do what you're asking, but you can sometimes use infix sections as an alternate to flip. Using your last example:

g . (`f` 2) . k

Also, I'd like to point out that sometimes it can help if you reorder the arguments your functions take. For instance, if you have a function that will often be partially applied to one argument in particular, you should probably make that the first argument.

Say you are implementing a data structure that represents a 2D game board (like you might for a Chess program), you would probably want the first argument to your getPiece function to be a Chess board and the second argument to be the location. I think it is likely that the location being checked would be changed more often than the board. Of course, this doesn't fix the general issue (maybe you want to check the same location in a list of boards) but it can alleviate it. When I decide on an argument order, this is the main thing I consider.



回答5:

Other things to consider:

Define helper functions for the partial application patterns (either locally, or globally if you find yourself using a small number of patterns several times).

fix_2 f a = \x -> f x a
fix1_3 f a b = \x -> f a x b

h = g . fix_2 f 2 . k

Not quite as nice as your hypothetical "blank" syntax, but okay; you can read fix_2 as a tag identifying the partial application scheme in use1.

Note that you never need any partial application schemes that don't fix the last argument; with currying fixing the first and third arguments of a 4 argument function (leaving a two argument function) is the same as fixing the first and third argument of a 3 argument function.

As a more involved idea, I believe you should be able to write a Template Haskell quasiquoter that actually implements your "underscores are parameters of an implied function" pseudosyntax. This would allow you to write expressions lie this:

h = g . [partial| f _ |] . k

More syntactic overhead than a helper function, and you still need to involve an additional name (to identify the quasiquoter), but perhaps easier to read if your partial application scheme is much more complicated:

h = g . [partial| f 1 _ 3 4 _ 6 |] . k

It means a bunch of work implementing the Template Haskell code though, which I've never done so no idea how much. But then you'd have arbitrary partial application schemes, whereas the helper function route requires a manual definition for each pattern.


1 Note that fixing only the second argument already has a standard helper function: flip. Partially applying to the second argument might not intuitively sound like swapping the first two arguments, but thanks to lazy evaluation and currying they are actually the same thing!