Partially apply several functions in Haskell

2020-04-11 06:36发布

Suppose, in Haskell, I have a bunch of functions that all depend on the same parameter type:

f :: Par -> a -> b
g :: Par -> b -> c

As I'm writing more of these functions that still depend on this parameter type, I can do something like

h :: Par -> a -> c
h par = myg . myf
    where myf = f par
          myg = g par

However I keep having to write these where lines. The question is: can this be avoided?

[Edit: I tried to provide a minimal example to illustrate the problem but apparently the example is too minimal to illustrate what I want. In the actual problem h is of course not just the composition of f and g. So here is some actual code:

There are functions

apply :: ChamberLattice -> ChLatword -> ChLatWord
reduce :: ChamberLattice -> ChLatWord -> ChLatWord

and I am defining a function

chaseTurn :: ChamberLattice -> Turn -> Parity -> ChLatWord -> ChLatWord
chaseTurn cl Straight _ xs = xs
chaseTurn cl t parity xs = if ((turn parity xs) == t)
                           then case myApply xs of
                               (y1:y2:ys) -> (y1:y2:(myChaseTurn t parity ys))
                               ys -> ys
                           else myReduce xs
where myApply = apply cl
      myChaseTurn = chaseTurn cl
      myReduce = reduce cl

]

(This question is essentially the same as Grouping functions in Haskell but there I used some unfortunate words that distracted people.)

4条回答
ら.Afraid
2楼-- · 2020-04-11 07:11

This can be done using implicit parameters (which are not pure Haskell but a ghc language extension, see https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#implicit-parameters).

The code above just becomes

f :: (?p :: Par) => a -> b

g :: (?p :: Par) => b -> c

h :: (?p :: Par) => a -> c
h = g . f
查看更多
Ridiculous、
3楼-- · 2020-04-11 07:12

In Haskell, all functions take one input argument. Sometimes, though, the return value of applying a function is a new function. As a first step, then, you can make that more explicit by putting brackets around the return value of your functions f and g:

f :: Par -> (a -> b)
g :: Par -> (b -> c)

Functions are types as well, so we could arbitrarily decide to alias a -> b to φ (phi instead of f) and b -> c to γ (gamma instead of g). (Yes, when you run out of letters, you reach for the Greek alphabet!)

This means that you can view your functions as having the types

f :: Par -> φ
g :: Par -> γ

These are both automatically instances of the so-called reader monad, which is also an (applicative) functor. Particularly, (->) Par, or, if it helps, Par ->, is an Applicative instance. This means that you can use pure and <*> with it.

As a first attempt, you can write something like

pure (\x y -> (x, y)) <*> f <*> g

in order to simply understand how that composition works. That expression has the type Par -> (φ, γ), so to speak. That lambda expression simply takes x from the f 'container', and y from the g 'container', and combines them in a tuple. The first element of the tuple has the type φ, and the second element has the type γ.

Plugging in the definitions of φ and γ, you get the type Par -> (a -> b, b -> c).

Instead of a return value as a tuple of functions, you want to compose these functions. You can use the function composition operator . for that:

h = pure (\x y -> y . x) <*> f <*> g

Notice that the functions compose from right to left, so x (a -> b) comes first, followed by y (b -> c).

You can, however, flip f and g around:

h = pure (\y x -> y . x) <*> g <*> f

That explicit lambda expression can then be eta-reduced to:

h = pure (.) <*> g <*> f

Finally, instead of writing pure (.) <*> you can use the infix <$> operator:

h = (.) <$> g <*> f

This function has the type Par -> a -> c.

查看更多
Emotional °昔
4楼-- · 2020-04-11 07:14

You've discovered the use case for the Reader monad, if you can adjust your signatures slightly. If you have

f :: a -> Par -> b
g :: b -> Par -> c

you can redefine them as

import Control.Monad.Trans.Reader

f :: a -> Reader Par b
g :: b -> Reader Par c

Then you can define h using the normal Kleisli composition operator.

import Control.Monad

h :: a -> Reader Par c
h = f >=> g

(Even without changing the signatures, I think you can write h = flip (flip f >=> flip g).)

查看更多
▲ chillily
5楼-- · 2020-04-11 07:20

You're doing h par = f par . g par a lot, and the par stuff starts to clutter.

You can't do h = f . g, since the par argument must be passed along, too.

So you come up with a high-powered composition operator that will do this for you:

-- (.) :: (b -> c) -> (a -> b) -> a -> c
(§) :: (par -> b -> c) -> (par -> a -> b) -> par -> a -> c
(§) f g par = f par . g par

Now you can do h = f § g. This operator was probably invented before.

Incidentally, partially applied functions are instances of Monad. This means you can do:

(§) f g par = (do { fpar <- f; gpar <- g; return (fpar . gpar) }) par

Or just:

(§) f g = do { fpar <- f; gpar <- g; return (fpar . gpar) }

(Here, fpar is f to which an implicit par has been applied. The monad instance makes par implicit.)

If we were to parameterize this do-block:

(§) f g = ( \f m1 m2 -> do { x1 <- m1; x2 <- m2; return (f x1 x2) } ) (.) f g

And eta-reduce the parameters:

(§) = ( \f m1 m2 -> do { x1 <- m1; x2 <- m2; return (f x1 x2) } ) (.)

And look on Hoogle for something that looks like this do-block, you'd find liftM2:

(§) = liftM2 (.)

At which point we don't really need to give it a special name, since liftM2 (.) is already pretty short.

查看更多
登录 后发表回答