In the past couple of posts, I’ve been talking about functional composition in regards to explaining its relevance to C#. I thought I’d step back just a little though and explain more of the fundamentals in a more natural functional language such as Haskell or F#. C# has a tendency to be a bit unnatural when it comes to explaining such concepts that I hope this becomes clearer.
A Simple Example
In order to better understand functional composition and partial application and how they can fit together, let’s take a simple example of determining whether a given Map in Haskell is not empty. For consistency sake, I’ll also provide an F# solution as well.
When someone is first starting out learning a functional language, many of these concepts such as partial application, currying and functional composition can seem foreign. When we’re a beginner, we might write a function given the above scenario of determining whether a Map is not empty such as this in the interactive console:
Prelude> import Data.Map Prelude Data.Map> :t Data.Map.null Data.Map.null :: Map k a -> Bool Prelude Data.Map> let notNull m = not (Data.Map.null m) Prelude Data.Map> :t notNull notNull :: Map k a -> Bool Prelude Data.Map> notNull (fromList [("foo","bar"),("bar","foo")]) True Prelude Data.Map> notNull empty False
But, if we remember to our previous post on the Application operator, we realize we can get a quick win here by being able to remove parentheses. Let’s relook at that operator ($) in how it can clean up our code.
Prelude Data.Map> :t ($) ($) :: (a -> b) -> a -> b Prelude Data.Map> let notNull m = not $ Data.Map.null m Prelude Data.Map> notNull $ fromList [("foo","bar"),("bar","foo")] True Prelude Data.Map> notNull empty False
So far so good! But, I’m still not satisfied, however. We can also look to drop the explicit map parameter to our function and partially apply the call to null. By using the functional composition ( . ) operator in Haskell, we can compose the not and the partially applied null functions together such as the following:
Prelude Data.Map> :t (.) (.) :: (b -> c) -> (a -> b) -> a -> c Prelude Data.Map> let notNull = not . Data.Map.null Prelude Data.Map> notNull $ fromList [("foo","bar"),("bar","foo")] True Prelude Data.Map> notNull empty False
There we are, much better! By utilizing functional programming techniques, we are able to make our functions clean and concise. You’ll notice that the functional composition operator in Haskell goes from right to left. Why it makes sense is that you are reading the final desired function call to be last to understand what computation was desired. In F#, I find myself often going back and forth between left to right composition and right to left.
Speaking of F#, we could also apply the same lessons here as well. Let’s start out with our beginners function once again using F# interactive:
> let isNotEmpty m = not (Map.is_empty m);; val isNotEmpty : Map<'a,'b> -> bool > isNotEmpty Map.empty;; val it : bool = false > isNotEmpty (Map.of_list [("foo","bar");("bar","foo")]);; val it : bool = true
As we discovered in the last post, we have the Application operator also in F# which looks like the inverse of the forward operator ( <| ) instead of ( |> ). Let’s rewrite this to rid ourselves of the parentheses once again:
> (<|);; val it : (('a -> 'b) -> 'a -> 'b) = <fun:clo@11> > let isNotEmpty m = not <| Map.is_empty m;; val isNotEmpty : Map<'a,'b> -> bool > isNotEmpty Map.empty;; val it : bool = false > isNotEmpty <| Map.of_list [("foo","bar");("bar","foo")];; val it : bool = true
And finally, we can then partially apply the Map.is_empty function to be able not have to explicitly provide the named parameter m. We might be able to do something like this:
> let isNotEmpty = not << Map.is_empty;; let isNotEmpty = not << Map.is_empty;; ----^^^^^^^^^^^ stdin(12,5): error FS0030: Value restriction. Type inference has inferred the si gnature val isNotEmpty : (Map<'_a,'_b> -> bool) Either define 'isNotEmpty' as a syntactic function, or add a type constraint to instantiate the type parameters.
Ouch, as we’ve discovered here, we’ve run into a value restriction problem. Instead, we have to be explicit about what types we’re using for this particular function. We can fix this by annotating the function with a full type signature such as the following:
> let isNotEmpty<'k,'a> : Map<'k,'a> -> bool = not << Map.is_empty;; val isNotEmpty<'k,'a> : (Map<'k,'a> -> bool) > isNotEmpty Map.empty;; val it : bool = false > isNotEmpty <| Map.of_list [("foo","bar");("bar","foo")];; val it : bool = true
At this point, it’s debatable whether it is cleaner in F# to partially apply this function given the extra requirements levied upon us. I still prefer partial application over the previous solution, but your mileage may vary.
One More Example
Let’s walk through another example that is a little more complex, although not much more than the previous. Taken from the Real World Haskell book, we want the ability to count the number of capital letters in a given string. To do this, the code in Haskell might look like the following:
Prelude Data.Char> let capCount = length . filter (isUpper . head) . words Prelude Data.Char> capCount "Hello there, Mom!" 2
What we’re doing is first splitting on the words, then filtering on whether the first letter is upper case, then counting them. Seems simple enough.
We can also do this in F# of course with just a small addition to create the words function. I’ll cheat and instead of using folds or recursion to solve it, I’ll just use a quick Regex to do the same. The words function is also partially applied in that I didn’t explicitly name our string parameter. Instead, at the end I first get the Regex matches, then cast the MatchCollection to an IEnumerable<Match>, then I map each element to get the string value. In this instance I used left to right functional composition, just to show you that I can use both. Let’s open our F# interactive and get typing!
> open System;; > open System.Text.RegularExpressions;; > let words = - let r = new Regex @"\w+" - r.Matches >> Seq.cast<Match> >> Seq.map (fun m -> m.Value);; val words : (string -> seq<string>) > let capCount = Seq.length << Seq.filter (Char.IsUpper << Seq.hd) << words;; val capCount : (string -> int) > capCount "Hello there, Mom!";; val it : int = 2
Looking at the logic for the capCount, it looks pretty much the same. First, we split on the words, then we filter based upon the first letter being uppercase, and then finally computing the length. There we have it, nice and simple functional composition and partial application.
After these two quick examples of functional composition and partial application, you now should get the idea that we’re able to create concise functions through code reuse. Instead of creating lambda expressions everywhere, we’re reusing functions we’ve already defined and composing them together to give us a rich programmatic model. As often as lambda expressions are nice, I try often not to use them. Instead, by reusing existing functions and composing them together, we can create more readable solutions as we can better understand the function’s definition.