A little known fact about me is that I took Wing Chun lessons for four months back in 2006. A student starting in Wing Chun will begin by learning the first form, called Siu Lim Tao (小練頭). This form’s name translates to “little idea,” and it provides the foundation in which succeeding forms and techniques depend upon.
I’ve spent a lot of time thinking about the little ideas behind the art of programming. In particular, the topic of functional programming keeps cropping up every once in a while. Some people love it, some people hate it, while others just have no idea what functional programming is all about.
When you ask people about functional programming, you might hear jargons like monads, morphisms, or semigroups. Others may say functional programming is all about map-filter-reduce. And you may also hear talks about purity, and side effects.
Implementing YouTube instant search
Before we begin exploring functional programming concepts, let’s take a look at our sample problem - a YouTube search.
The YouTube search app will allow the user to type in search terms into an input box. As the user types, video results will appear below the input box.
OK, now we are ready to begin!
The Little Idea
There are three concepts that capture the essence of functional programming.
Data in, data out
Code as data
(Function) Composition all the way down
Other functional programming concepts and techniques can be built upon this little idea.
Concept #1 - Data in, data out
The first concept is the separation of data and behaviour. Data by themselves cannot do anything, but functions can be used to take them in as input and then output some new data. You can picture a function as a box that take in some shape, and spits out another.
In the illustration above, function f takes a triangle as input, and outputs a square. Function g takes in a square and outputs a circle.
When working with data in, data out, it is important to enforce function purity. A function is pure if its output is solely determined by its input. That is, given the same input, it must always return the same output. Furthermore, a pure function must not have any side effects. An effect is something like writing to database, changing a global variable, or throwing exceptions.
With this idea in mind, we can implement a couple of functions to be used in our app.
Code - Mapping search term to URL
As the user is typing into the input box, we need a way to use that value and transform it to an URL to fetch the results.
Here, we take in a
String as input, and ouput an
Url. Note that
Url is simply an alias
String but has better semantics.
Now that we can map the user’s input value to an URL, we can fetch an API call to fetch the response JSON.
So let’s look at the
VideoJSON object, and think about how we can build
Video objects from them.
Code - Hash to Video
The YouTube API returns a
VideoJSON as follows.
Therefore, we can map it to a
Video type as follows.
toVideo :: JSON -> Video). It describes the input and output of the declared function.
Concept #2 - Code as data
The second concept is more commonly referred to as higher-order functions. It simply means that functions can be used as input to other functions; and that functions can output new functions.
For example, given the
toVideo :: JSON -> Video function above, we can use it to map over an array
of JSON objects.
method takes in a transformation function as input, and returns a new array with the original elements
transformed by the input function.
The downside of the
Array.prototype.map method is that you can only call it in the context of the input data. As
an alternative, we can use Ramda’s
map function, which will
take in a transformation function as input (same as before), and then output a new function that can be called with
mappable objects (such as Array).
mapVideos function is now completely modular and can be used with any array containing
to get an array of Video objects.
Note: The output function from
map is called a transducer.
Transducers are composable transformations that are decoupled from input data. You can watch Rich Hickey's
talk on transducers to learn more about them.
Concept #3 - Composition all the way down
The third concept is that we can compose two functions (f and g) together (g ∘ f) as long as f’s output matches g’s input.
In the illustration above, we created a new function h that composes f and g. This new function takes
Taking this idea further, if we had another red function as follows – taking in a nested data that includes triangle, then outputs triangle.
Then, we can compose red and h together to take in the nested data, and output circle.
This allows us to start with simple functions and use composition to build up more powerful functions. The advantage of this approach is that the functions are completely modular. All you have to do is make sure that the inputs and outputs match while composing.
So, let’s compose a few functions for our app!
Code - Mapping JSON response to videos
mapVideos function that we previously implemented takes an array of
VideoJSON objects as input, and
ouputs an array of Video objects (
mapVideos :: [VideoJSON] -> [Video]). However, the YouTube API actually nests the array of video
JSON objects inside a structure like this:
We can retrieve the
VideoJSON objects with the
prop function, which
returns a property (by name) when called with an object.
Now, if we pass the response JSON to
prop('items'), we will get an array of
VideoJSON objects, which
is exactly what
mapVideos takes as input. This is all we need to define the
Building a function to search videos
Let’s recap what we have so far.
makeUrl that takes user input
String and outputs an
toVideos that takes
ResponseJSON and outputs
So we just need a function that can take in
Url and output
ResponseJSON. Well, it’s not as simple as that
since we are dealing with an API call that goes over the network, thus we cannot return a simple value.
In this case, I am opting to use the
which represents a delayed computation. Think of it as a Promise, but does not have an effect right away.
Now the function
httpGet is defined as follows.
And then we can define the final
searchVideos function as follows.
function is used inside the composition. This is
needed since the return type of
Task Error ResponseJSON, we
need to lift the
ResponseJSON -> [Video] transformation function into
a function that can operate on the future resolved value of the Task.
The type for
M ResponseJSON -> M [Video], where
M is a mappable object -- in this case we are using a Task.
Wiring up the UI
Now comes the easy part of putting the UI togther to use the
we just composed.
Here is the main
And here is the less interesting
The main thing to note is that the both the
VideoSearch components are very simple.
All of the complexity is delegated to the
searchVideos function, which as we’ve seen, is made up
of a bunch of smaller functions.
Dealing with changing requirements
Let’s consider a new requirement: We want to display a message to the user if no search term is entered.
To implement this, we will need a way to determine if the search has not been performed. We can use
to achieve this, and change the
searchVideos function to return
Task Error (Either Empty [Video])
Task Error [Video].
Either a b means the structure may contain a value of type
a (Left) or type
b (Right). The Left value represents an exceptional
value, which in our case we will use to represent an empty search state.
Handling the case of not having an URL
Now, to determine whether a search can be performed in the first place, we can create a new
maybeMakeUrl :: String -> Maybe Url. Note that we are returning a Url in the context
Maybe can be of type
Maybe.Nothing, with the latter representing the case
of no value. (Note how we avoided using
HTTP GET using a Maybe URL
So now that we have a function that returns
Maybe Url, we also need a new version of
Maybe Url instead of just
The Maybe type provides a
cata method that will allow us to pattern match whether we have
Just Url or
Nothing (think of it as using
x instanceof Maybe.Just and
x instanceof Maybe.Nothing).
Just case, we are using the
lift function to transform the
ResponseJSON object inside the Task
Nothing case, we are returning a Task that contains the empty type
Empty in our case). This
is similar to using
Promise.resolve(...), but for Tasks.
cata method uses a concept called Catamorphism
(from Greek components "down" + "shape"). It provides a generalized way of collapsing a type based on its structure.
If you want to learn more about catamorphism, I found the Introduction to recursive types post of F# for fun and profit to be a good read.
Refactoring the search videos function
Now that we have the two new functions
maybeHttpGet, we can change
searchVideos to the following.
Notice that all other functions remain unchanged (
We simply introduced two new functions, and refactored the final search function to handle Maybe and Either.
lift(lift(...)) call is made because we need to lift the
toVideos transform function twice:
- Once for lifting the transform to operate with
Either Empty ResponseJSON.
- Second time for lifting the lifted transform to operate with the future resolved value of type
Refactoring the component to work with Either
And finally, we refactor the
SearchVideos component to reduce the rendered element based on whether we have
a right value (search performed), or a left value (search not performed).
You can view the full source code by visiting the repository on GitHub.
The power of the functional approach is that what works in the small also works in the large. We started with
a simple function
makeUrl, and eventually ended up with the more powerful function
searchVideos. Along the entire
way we were using the same approach of thinking about input/output types, and function composition.
A nice benefit of having these pure functions is that we can easily use and test each of them separately. For example,
lift(lift(toVideos)) function can be called with input of type
Task Error (Either e ResponseJSON).
In this post, I presented the little idea behind functional programming in terms of three concepts.
Data in, data out - separating data and behaviour
Code as data - using functions as data
Composition all the way down - composing simple functions to form more powerful functions that are composable themselves
These three basic concepts will help you keep your application more modular by allowing you to mix and match functions together (like a Lego!) as long as their types match up. This functional approach is something that works both in the small and in the large.
If you are interested in learning more functional programming, I’ve included some videos below that were influential to me.
View the final source code for the example on GitHub.
[Video] Check out Classroom Coding with Prof. Frisby for more functional programming with ReactJS.
[Video] Functional Programming for OO Development - Jessica Kerr
[Video] Destructuring Functional Programming - Gilad Bracha
[Video] Functional Programming from First Principles - Erik Meijer
[Video] Functional Programming Design Patterns - Scott Wlaschin
[Video] Railway oriented programming: Error handling in functional languages - Scott Wlaschin