Friday, December 20, 2013

Making a Piña Colada in Haskell: It's All About Concurrency

I was reading The Pragmatic Programmer this morning, and it got me to thinking about Haskell.

Consider the following "function" for creating a piña colada:

  • Open blender
  • Open piña colada mix
  • Put mix in blender
  • Measure 1/2 cup white rum
  • Pour in rum
  • Add 2 cups of ice
  • Close blender
  • Liquefy for 2 minutes
  • Open blender
  • Get glasses
  • Get pink umbrellas
  • Serve

It's very easy to understand and very linear.

Now consider the following diagram that conveys which parts can be done concurrently:

This description of the recipe is quite a bit more complex, but it's a lot more obvious which things can be done concurrently.

There are a lot of approaches to concurrency. For years, we've relied on our CPUs to give us some implicit concurrency. The CPU can look at the code at a very micro level and figure out which assembly instructions can be done concurrently because they're working with different parts of memory, etc.

Threads and processes also provide concurrency, but they're at a very different level, and it's very far from implicit.

Node.js also provides concurrency. However, telling Node.js which things can be done concurrently while responding to a request still takes a lot of careful thinking. You don't have to do anything to get Node.js to handle multiple requests at the same time. However, if you need to make three REST calls in order to respond to a particular request, it's up to you to notice whether or not you can do those calls concurrently, and if you do decide you can do them concurrently, it still takes some explicit coding to make it happen.

One of the essential problems is that it takes work to get from that nice linear description of how to make a piña colada to one in which all the opportunities for concurrency are explicitly stated. That's what got me thinking about Haskell again. Maybe laziness isn't such a bad idea after all ;) In Haskell, it's a lot easier to separate describing the steps necessary to do something from actually taking those steps. You can describe the steps in a way that makes sense to you, but let Haskell decide at runtime what order to take those steps in. Admittedly, I'm hand waiving a lot, and I haven't actually read Parallel and Concurrent Programming in Haskell, but I just have this feeling that Haskell makes you think in a way that the concurrent description of the recipe above will just kind of happen naturally.

Here's a fun exercise for the reader. Write a program that implements each of the steps above by doing a REST call. For instance, to "Open blender", just do a post to some server with the string "Open blender". The server doesn't really have to do anything (we'll just pretend). Implement the entire recipe using the opportunities for concurrency provided by the diagram above. Can your system figure out what things can be done concurrently automatically and then do them concurrently automatically? Bonus points if you can you make multiple piña coladas at the same time. Double bonus points if you can write code that is somewhat readable by someone unfamiliar with your system.

Footnotes:

There are a lot of things related to what I'm talking about such as Flow-based programming, the Actor model, etc. On the other hand, perhaps there are no silver bullets.

I'm not a real Haskell programmer or a Node.js programmer.

In fact, I've never even had a piña colada, so I could be way off base here ;)

3 comments:

proppy said...

is GNU `make` allowed? :)
pinacolada: serve
@echo piña colada
serve: get_glasses get_pink_umbrellas open_blender_again
@echo serve
open_blender_again: liquefy
@echo open blender again
liquefy: close_blender
@echo liquefy
get_glasses:
@echo get glasses
get_pink_umbrellas:
@echo get pink umbrellas
close_blender: put_mix_in add_two_cups_ice pour_in_rum
@echo close blender
put_mix_in: open_mix open_blender
@echo put mix in
add_two_cups_ice: open_blender
@echo add two cups ice
pour_in_rum: measure_rum open_blender
@echo pour in rum
measure_rum:
@echo measure rum
open_blender:
@echo open blender
open_mix:
@echo open mix

$ make -j -f pinacolada.make
get glasses
get pink umbrellas
open mix
open blender
measure rum
put mix in
add two cups ice
pour in rum
close blender
liquefy
open blender again
serve
piña colada

Shannon Behrens said...

Nicely done, proppy!

Paddy3118 said...

Hi, you seem to have two processes with the same name: "Open blender".

You probably want to use two names, maybe:
Open blender when empty
and:
Open blender when mixed.

Cheers, Paddy