I am getting into some point free notation with the goal to grasp the benefit of this style.
Doing so, I am facing difficulties with funtions composition.
Here is the problem I have in its most simple form:
f x y = x + y
g x = x * 2
h x y = g(f x y) – how to “point free” the function h ?
– What I was thinking off is
h = g . f
– however, the compiler rejects this (types do not match)
– I also tried
h = g $ f
– this is also rejected.
– what I do not get either is that the types computed by the compiler are different.
It looks that I am on a wrong path regarding functions composition.
Some help would be appreciated.
You can always use http://pointfree.io/ to automatically find the pointfree form of any expression. Usually it will be quite hard to read.
In your case the main problem is that normal function composition only works on functions that take one argument. So g . f will not work, because f should be applied to two arguments.
That website gives the following pointfree expression:
h = (g .) . f
The idea is that f is applied to its first argument by the dot to the left of it. Then it needs to be applied to its second argument, which requires the second dot that is to the right of g.
Maybe you can understand it better if you inline the definition of (.) step by step:
h x y
= (((g .) . f) x) y -- pointfree defn of h (with redundant parentheses)
= ((g .) (f x)) y -- defn of (.)
= (g . (f x)) y -- write out sectioned (g .)
= g ((f x) y) -- definition of (.)
= g (f x y) -- remove unnecessary parentheses
This an example of equational reasoning that is one of the main selling points of Haskell.
Thanks belka: this perfectly works.
Can you please clarify why?
– Let’s say:
f :: Int->Int->Int – this function returns an Int
g :: Int ->Int – this function takes an Int has argument
h = g . f – why is this rejected while the output of f perfectly suits the input of g ?
– if I have
f2 :: Int->int
h2 = g . f2 – works perfecly ( output of f2 also suits input of g).
Thanks also for sharing your opinion about point free notation.
For now, based on my first trials, I share the same standpoint: the benefit is for simple, kind of mathematical, function composition.
I’m somebody who uses point free style a lot. I do think it’s readable, and I think it guides me into writing proper abstractions.
As you’ve discovered, point free doesn’t work well with functions that take more than one argument! I would not recommend using point free with multi-argument functions, because as others have pointed out, it gets hard to read pretty fast.
Don’t forget to take currying into consideration, though. Every function of multiple arguments is really a function of one argument that returns another function. f :: a -> b -> c should be read f :: a -> (b -> c).
Take map :: (a -> b) -> [a] -> [b] for example. For some function f :: a -> b, the expression map f is a function with type [a] -> [b]. That just has one argument, so you can write something like map f . g without getting into complex point free-ness.
The reason I like point free is that it exposes the pipeline nature of many programs. I use <=< for the same reason. (See http://www.haskellforall.com/2012/08/the-category-design-pattern.html, too.) Some people prefer to turn the arrows around with & and >=>, which also makes sense. But I already bit that bullet studying mathematics, and rewriting f (g (x)) as f . g is just something I’m used to.
Being a beginner in haskell, point free notation looks confusing to me as soon as multiple arguments are involved. Even the example given by @chreekat is unclear to me regarding operators precedence. Indeed,
– can I write
foo = map f . g
– while one should use parenthesis for
foo xs = map (f . g) xs
Regarding the point free solutions for the example I gave, I still struggle to understand/decompose the syntax.
In the solution:
h = (g .) . f
First, (g .) is a partial application of . with g being the first and only argument. The resulting type is:
(g .) :: (a -> Int) -> a -> Int
Then . is applied with the first argument being (g.) and the second being f.
Here, I don’t get how the resulting type becomes :
The other solution :
h = (.) g . f
is equivalent to the preceding solution as the expression “(.) g” is the same as “(g .)”
Note that these two examples have different meanings.
If you define
foo = map f . g
foo xs = (map f . g) xs = map f (g xs)
But if you define
foo = map (f . g)
foo xs = (map (f . g)) xs = map (f . g) xs
The difference is that, in the first case, g is applied once to the whole list and then f is mapped over the result. In the second case, g is part of the mapping and applied to each element before applying f.
So, in this case we know the following:
f :: Int -> Int -> Int
g :: Int -> Int
(.) :: (b -> c) -> (a -> b) -> (a -> c)
You have already seen that (g .) :: (a -> Int) -> a -> Int. This can be shown using a process called unification. In this case we unify the type of the first argument of (.), which is (b -> c) with the type of g, so we get the following:
Int -> Int
~ b -> c
(here ~ mean type equality)
This clearly implies that Int ~ b and Int ~ c. We can use these equalities to determine the type of (g .). We retain the remaining type of (.) without the first argument and rewrite b to Int and c to Int:
(g .) :: (a -> b ) -> (a -> c )
-- rewrite b to Int and c to Int
(g .) :: (a -> Int) -> (a -> Int)
We can do the same to determine the type of (g .) . f. We first unify the type of the first argument of (.) with the type of (g .):
(b2 -> c2 )
~ ((a -> Int) -> (a -> Int))
So b2 ~ (a -> Int) and c2 ~ (a -> Int). Note that these b2 and c2 are new and should not be confused with the old b ~ Int and c ~ Int used in the previous unification. Also note that I have made it very easy to see the equalities because I have aligned the two types, finding the right alignment might be tricky at first. The basic idea is to match up the top level ‘->’ in both equations. In this example I have also made it easier by adding the redundant parentheses around the last (a -> Int): (a -> Int) -> (a -> Int) can also be written without redundant parentheses as (a -> Int) -> a -> Int.
Okay, so now we can apply these two equalities:
((g .) .) :: (a2 -> b2) -> (a2 -> c2)
-- rewrite b2 to (a -> Int) and c2 to (a -> Int)
((g .) .) :: (a2 -> (a -> Int)) -> (a2 -> (a -> Int))
-- remove redundant parentheses
((g .) .) :: (a2 -> a -> Int) -> a2 -> a -> Int
Finally we will apply this function to f, so we should unify the first argument with the type of f:
a2 -> a -> Int
~ Int -> Int -> Int
Clearly, a2 ~ Int and a ~ Int, so:
(g .) . f :: a2 -> a -> Int
-- rewrite a2 to Int and a to Int
(g .) . f :: Int -> Int -> Int