user100106
user100106

Reputation: 141

Haskell: applying flip twice (type of flip flip)

I am learning Haskell along some basic functions. I was doing some exercises with Flip, which takes a function of two arguments and evaluates the result flipping the order of the arguments. Consider the function flip flip, I would have thought, following the definition of flip, that it flipped the arguments twice, evaluating the original function with the parameters in the original order. When I checked this assumption with ghci checking the function type, it yielded:

flip flip :: b -> (a -> b -> c) -> a -> c

I don't understand why this is the function type of flip flip. It takes parameter b and parameter (a -> b -> c) and yields a function a -> c. Why is this the case ? I would really appreciate an explanation since I am at lost with this. Thanks in advance

Upvotes: 3

Views: 672

Answers (4)

Aky
Aky

Reputation: 1817

Plenty of good and thorough answers here, but I'd just like to add a practical example that shows an expression with flip flip being evaluated:

     flip flip 2 (>) 4
-->  flip (>) 2 4 
-->  (>) 4 2
-->  True

If it helps, you may introduce a pair of parentheses to the original expression so you can really "taste the curry": (flip flip 2 (>)) 4.

As to why would you like to do something like that: even though I'm far from a Haskell expert, if for some reason you're determined to write a function in point-free notation, flipping a flip can come in useful. For instance - in light of the example above - if you wish to define f by the equation f a g b = g b a then f = flip flip (as this answer already points out).

Upvotes: 0

Bergi
Bergi

Reputation: 665455

Flipping twice would be \f -> flip (flip f), or flip . flip. This would indeed have the type (a -> b -> c) -> (a -> b -> c).

What you are doing here instead is applying flip on the flip function, i.e. flipping flip's argument order. So if we start with

flip :: (a -> b -> c) -> b -> a -> c
-- and, as the type of the argument
flip :: (a' -> b' -> c') -> b' -> a' -> c'

then if we match the types

a = (a' -> b' -> c')
b = b'
c = a' -> c'

we get the result

flip flip :: b' -> (a' -> b' -> c') -> (a' -> c')

Upvotes: 13

DarthFennec
DarthFennec

Reputation: 2778

Let's look at the types:

flip :: (a -> b -> c) -> b -> (a -> c)
flip :: (d            -> e ->  f     ) -> e ->  d            ->  f
flip flip ::                              b -> (a -> b -> c) -> (a -> c)

In other words, flip reverses the first two arguments of its argument, and the first two arguments of flip are the function to flip and the second argument to that function. So instead of taking arguments in the order 'function', 'second argument', 'first argument', the order becomes 'second argument', 'function', 'first argument' when you flip it.

If you want to flip, and then flip back, you do something like this:

doubleflip x = flip (flip x)

Or equivalently:

doubleflip = flip . flip

The (.) operator feeds the output of the right-hand side into the left-hand side.

Upvotes: 6

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477686

You do not apply the flip function twice. If you want to apply flip twice, you are looking for:

flip . flip :: (b -> a -> c) -> b -> a -> c

What you here do is flipping the flip function. So it takes flip as the function to flip.

We can resolve the type of flip1 flip2 (I here use subscripts to make it clear to which flip we are referring) as:

flip1 :: (a -> b -> c) -> b -> a -> c
flip2 :: (d -> e -> f) -> e -> d -> f

Since flip2 is the parameter of flip1, so that means that the type of flip2 is the same type of the parameter of flip1, so that means that:

        a       -> (b ->    c    )
~ (d -> e -> f) -> (e -> (d -> f))

Hence that means that a ~ (d -> e -> f) (the type a is the same as d -> e -> f), b ~ e and c ~ (d -> e). So the type of the function flip1 flip2 is the type of the output type of flip1, but with the equivalences, so that means that:

flip1 flip2 :: b -> a -> c
flip1 flip2 :: e -> (d -> e -> f) -> (d -> e)

We basically thus made a function that first takes the second parameter, then takes the function, and then the first parameter, and then calls that function with the flipped parameters. So if flip2 = flip flip, it is implemented like:

flip2 :: e -> (d -> e -> f) -> (d -> e)
flip2 y f x = f x y

Upvotes: 4

Related Questions