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