Partial application using flip
Published on October 15, 2019 under the tag haskell
I have been writing Haskell for a reasonable time now – I believe I am coming up on ten years – so sadly the frequency with which I discover delightful things about the language has decreased.
This blogpost should be approachable for beginners, but when you’re completely new to Haskell and some terms are confusing, I would recommend looking at the Type Classes or Learn You a Haskell materials first.
A few extensions are required to show some intermediary results, but – spoiler alert – they turn out to be unnecessary in the end:
Currying and partial application
In Haskell, it is idiomatic to specify arguments that are unlikely to change in between function calls first.
For example, let’s look at the type of
This function allows us to insert an item into a map, or if it’s already there, merge it with an existing element. When we’re doing something related to counting items, we can “specialize” this function by partially applying it to obtain a function which adds a count:
And then we can do things like
increaseCount "apples" 4 basket. The extremely succinct definition of
increaseCount is only possible because functions in Haskell are always considered curried: every function takes just one element.
Sockets, Handles and more
However – there is a second idiomatic aspect of argument ordering. For imperative code, it is common to put the “object” or “handle” first.
base itself is ripe with examples, and packages like
network hold many more:
This allows us to easily partially apply functions to a specific “object”, which comes in useful in
In addition to that, it allows us to replace the type by a record of functions – as I went over in the handle pattern explanation.
Specializing top-level handle functions
However, we end up in a bit of a bind when we want to write succinct top-level definitions, like we did with
increaseCount. Imagine we have a
Handle to our database:
Some mock utility types:
And a top-level function to change a member’s plan:
If we want a specialized version of this, we need to explicitly name and bind
h, which sometimes feels a bit awkward:
We sometimes would like to be able to write succinct definitions, such as:
But is this possible? And what would
specialize look like?
Since this is a feature that relates to the type system, it is probably unsurprising that, yes, this is possible in Haskell. The concept can be represented as changing a function
f to a function
Of course, a function can be converted to itself:
Furthermore, if a
a below) is the first argument, we can skip that it the converted version and first supply the second argument, namely
b. This leads us to the following definition:
This is a somewhat acceptable solution, but it’s not great:
- type errors from incorrect usage of
Specializewill be hard to read
AllowAmbiguousInstancesmay required to defer instance resolution to the call site of
Again, not show stoppers, but not pleasant either.
Flippin’ partial application
The unpleasantness around
specialize is mainly caused by the fact that we need a typeclass to make this work for multiple arguments. Maybe using some sort of combinator can give us a simpler solution?
Because we’re lazy, let’s see if GHC has any ideas – we’ll use Typed holes to get a bit more info rather than doing the work ourselves:
We get an error, and some suggestions:
posts/2019-10-15-flip-specialize.lhs:152:18: error: • Found hole: _ :: (Handle -> Tier -> String -> MemberId -> IO ()) -> Tier -> t0 Where: ‘t0’ is an ambiguous type variable • In the expression: _ In the first argument of ‘_’, namely ‘changePlan `_` Premium’ In the expression: changePlan `_` Premium `_` "Halloween 2018 promo" • Relevant bindings include halloweenPromo3 :: Handle -> MemberId -> IO () (bound at posts/2019-10-15-flip-specialize.lhs:151:3) Valid hole fits include flip :: forall a b c. (a -> b -> c) -> b -> a -> c with flip @Handle @Tier @(String -> MemberId -> IO ()) (imported from ‘Prelude’ at posts/2019-10-15-flip-specialize.lhs:1:1 (and originally defined in ‘GHC.Base’)) seq :: forall a b. a -> b -> b with seq @(Handle -> Tier -> String -> MemberId -> IO ()) @Tier (imported from ‘Prelude’ at posts/2019-10-15-flip-specialize.lhs:1:1 (and originally defined in ‘GHC.Prim’)) const :: forall a b. a -> b -> a with const @(Handle -> Tier -> String -> MemberId -> IO ()) @Tier (imported from ‘Prelude’ at posts/2019-10-15-flip-specialize.lhs:1:1 (and originally defined in ‘GHC.Base’)) ...
Wait a minute!
flip looks kind of like what we want: it’s type really converts a function to another function which “skips” the first argument. Is it possible that what we were looking for was really just… the basic function
We can make the above pattern a bit cleaner by introducing a new operator:
Fascinating! I was aware of using
flip in this way to skip a single argument (e.g.
foldr (flip M.increaseCount 1)), but, in all the time I’ve been writing Haskell, I hadn’t realized this chained in a usable and nice way.
In a way, it comes down to reading the type signature of
flip in two ways:
Convert a function to another function that has the two first arguments flipped. This is the way I am used to reading flip – and also what the name refers to.
Partially apply a function to the second argument. After supplying a second argument, we can once again supply a second argument, and so on – yielding an intuitive explanation of the chaining.
It’s also possible to define sibling operators
///$, etc., to “skip” the first N arguments rather than just the first one in a composable way.
Should I use this everywhere?
… probably not? While it is a mildly interesting trick, unless it becomes a real pain point for you, I see nothing wrong with just writing: