Blog Post:

Higher-kinded fun in Haskell

Haskell Programming LanguageIn my previous post, I showed an example of where the lack of higher-kinded types in .NET (both C# and F#) induces the need for code repetition.  In this post I’ll show you how higher-kinded type support in Haskell allows for the elimination of this repetition.

LINQ

First, let’s take our LINQ query and translate it into Haskell syntax. As a reminder, here’s the query.

Haskell Translation

In Haskell syntax, that translates directly to:

Remember “IEnumerable<T>” in C# is (for our purposes) equivalent to “[T]” in Haskell.  Also “where” is equivalent to “guard”, and “return” is like “select”.

Haskell Use

Let’s use it in a full program:

The results:

Great! So now we can do LINQ-like queries in Haskell.

Higher-kinding things

But, remember in the previous post I said that LINQ is equivalent to a MonadPlus abstraction, which is a monad with filtering (where-clause) capability. And if you look above, you’ll see that getNotices also only uses the MonadPlus interface of the List class. So let’s abstract the type signature of this function.

Note we didn’t have to change the implementation at all.  All we had to do was to update the type signature to say “don’t limit me to a List, but rather allow anything that is a MonadPlus”.  We compile that, and great, no errors!

An Example

Now, Haskell does not have Rx, but let’s test this against one of the more common monads, the Maybe monad:

The results:

Awesome, so the same logic can be reused on List types *and* Maybe types, which cannot be done on .NET!

Going Crazy!

Now for a more complicated example, when looking at the documentation I noticed that STM (Software Transactional Memory) is also of type MonadPlus. What would that mean? Looking through the documentation of STM, you can find that when the guard fails, the transaction retries until it succeeds.  So it’s essentially a transaction that keeps on retrying not only until the execution was atomic, but also until the input data is what it wants. Great, how can we test this? Here’s a function I created to test it.

What happens here? The STM vars are initialized with incompatible person/purchase. So the guard fails, the transaction is retried (for the performance-concerned: it does not sit in a spin loop retrying the transaction, but smartly sleeps until either of the vars is updated) until the vars are compatible, and atomically blocks at the second-last line. So then in another thread, the STM var is updated to compatible alice, the transaction completes, the notice is returned, and the main thread prints it out.

Summary

Can I think of a real-world use case for using STM this way?  Well, probably not for logs, but maybe in a banking scenario where the transaction has to not only be atomic, but also ensure the account balance stays positive.  That could be a topic for another post.  But for now it’s just interesting to see how higher-kinded types allow one piece of logic to be used in so many completely different ways. I hope you enjoyed this exercise as much as I did.

Leave a Reply

Your email address will not be published. Required fields are marked *

Get in Touch

If you have a product design that you would like to discuss, a technical problem in need of a solution, or if you just wish you could add more capabilities to your existing engineering team, please contact us.