SK

a developer's blog

about

FP virgin learns Elm

Sunday evening our CTO emails us:

Reply to if you want to learn Elm, FP and FRP. This week you come: 5 times, every day, 1pm-2pm. You can bring your food/brown bag...Friday we will be present a html5 snake.

Fast forward one week and Boom! A fully functional snake! (Refresh page to restart).

Learning functional programming (FP) and functional reactive programming (FRP) had been on my long-term roadmap anyways. So I figured -- why wait?

Output of the week

My week of functional reactive snake broke down into 2 days adjusting to Elm syntax and the functional style, 2 days of staring confused as my colleagues discussed signal graphs, one evening reading others' code and the Elm documentation, and finally one solid day of writing my own code and refactoring. In the end, I felt like I was starting to get the hang of it, and I was proud that I produced the game above.

Why FP?

Why should I care about FP? What's the benefit for me?

Honestly the sheer number of people I hear talking about FP is enough to spark my curiosity and give it a try. A friend summarized the CRAFT conference saying "Yeah one of the overarching themes was definitely the shift from OOP to FP."

But why should I care about FRP if my focus is on Objective-C and iOS development?

My main motivation is to be able to use the Reactive Cocoa library. At Prezi we are using it so much now that our experienced Objective-C developers seem to be writing almost exclusively in Reactive Cocoa. Most of our teams are adopting reactive programming principles nowadays saying that it leads to cleaner, more declarative code. I was a little surprised to find out that even our backend API team is using reactive programming, thereby quashing my assumption that such a paradigm is only useful in client code.

Justification aside, it's always fun to learn new technologies right?

FP Virgin

I've been learning Objective-C/Cocoa pretty religiously for the last 4 months as a new member in the iOS team at Prezi. Before Objective-C, I was pretty familiar with Python and C, and now I feel quite comfortable with the object-oriented paradigm. My only FP experience would be my familiarity with Python's map, filter, reduce and lambda functions (if that counts).

Elm

Elm left a positive first impression on me. It's a functional language which compiles to HTML/CSS/Javascript which in itself is pretty cool. But if you need more reasons to get excited about Elm, here's a few.

Whitespace. Not a fan of braces? Me neither. I mean come on, who can resist a function definition which has neither parentheses nor braces:

flipDirection : Direction -> Direction
flipDirection direction =
  case direction of
    Up -> Down
    Down -> Up
    Left -> Right
    Right -> Left

UNIX-like pipelines. For those who like composing or chaining functions, Elm offers the |> operator as an alternative to grouping arguments with parentheses.

drawCell: Color -> Position -> Form
drawCell color cell =
  rect 10 10 |> filled color
             |> move (toFloat cell.x*10, toFloat cell.y*10)
             |> move (-92,0)

Natural separation of concerns. As I polished up my snake game, the code I had written naturally fell into one of 3 categories: model-logic, rendering, and signals. I thought I had gotten lucky, but then I heard Elm creator Evan Czaplicki mention in a talk that this usually happens. The main function brings these three branches together.

FP Syntax

The functional syntax at first sight has a few oddities.

Arrows. To declare the type of a function you say something like foo:int -> bool -> float. This was really annoying me until I realized that the majority of the time you can read it like a C function declaration float foo(int, bool). This isn't a perfect analogy because foo doesn't require 2 arguments. It's perfectly acceptable to pass foo just an int, and it will return a function which takes a bool and returns a float. In other words, the return type of a function is either the right most type (like float in this example) or a function with a signature equal to some suffix of the original signature.

Anonymous functions. Many languages support lambda functions, but when you write in FP you are somehow forced to use them more often. And what surprised me is how few characters are necessary to define one. For example, in Python I have to write lambda x, y : x + y whereas in Elm I write \x y -> x + y.

No for loops, no mutable objects. It's tough to change your way of thinking from "I'll just create a helper array which I populate using a for loop" to "I'll map my array through this function and then this function." Even doing a simple thing like checking for containment can look a little funny (that is, if you're a noob like me and not aware of convenience methods like any):

containsCell : Position -> [Position] -> Bool
containsCell c cells =
  length (filter (\cell -> sameCell c cell) cells) >= 1

Signals

Wrapping my brain around signals and the transformations you can apply to them was probably the most difficult part of the week. Here's what I managed to understand:

Signals are signals. What does a real-world signal do? It emits values over time. Like a heartbeat or a polygraph, a signal in FRP is just emitting values of a specified type over time.

Signals track events. Elm's standard library includes the Time, Http, Keyboard, and Mouse libraries where you are likely to find most of the signals you'll ever need. To create your own signal or transform a given signal you can make use of the the -- wait for it -- Signal library.

Once you go signal you never go back. I'm not talking about how addictive FRP is, rather the lift function in Elm. Because signals themselves persist (while their emitted values may change) it doesn't make sense to write a function which takes a signal and returns a nonsignal (which value of the signal would it use?). Instead you have functions which operate on nonsignal types and then you have functions which transform signals (take 1 or more signals and return a signal).

Elm's lift function glues the nonsignal and signal parts of your program together. Its type is

lift : (a -> b) -> Signal a -> Signal b

Here a and b refer to an arbitrary type. So the lift function can transform your function f:Int->Bool into f':Signal Int -> Signal Bool which you can then apply to a signal like Mouse.x for example.

This is where the natural separation of concerns comes from. You write your normal functions, then eventually you apply lift functions to integrate your model with signals, but once you're writing functions on the level of signals, there's no going back.

All roads lead to main. Your program's logic can be represented by a signal graph where all roads eventually lead to the master main function which has type Signal Element. Here's an example of main and lift in action (maybe this is specific to Elm and not FRP):

main : Signal Element
main = lift render gameStateSignal

gameStateSignal : Signal GameState
gameStateSignal =
  foldp (nextState board) (GameOn startSnake startApple) wrappedSignal

render:GameState -> Element
render state = case state of
    GameOn snake apple -> drawCells snake.cells apple
    GameOver score   -> asText ("GameOver. Snake Length: " ++ (show score)) 

Signals can remember. To maintain some sort of state in the stateless world of FP, you have to use the foldp function. Whenever a signal emits a value, it takes the previous value and the current value to do something interesting. So foldp to me means remembering and state-modification.

Conclusion

I really liked Elm and would be glad to work more with it. But I think the more important point is that it's given me a solid introduction into the world of FRP and in turn I'm more confident in using things like the Reactive Cocoa library.

Evan's explanation of FRP
complete code for my snake game which you can mess with at elm-lang.org/try