Emulating Objects in Functionally-Oriented Languages (The Real Midwives of Haskell)

Reading Time: 11 minutes

I’m working my way through Crafting Interpreters. The project guides programmers through building their own interpreters for the Lox programming language. I started writing a blog series about my progress. You can see all the posts so far right here.

In the last post covering Chapter 11, we talked about nested scopes. And eventually, we will move on to Chapter 12. But before I do that, I’ve been going back through the book and giving some of the exercises a more thorough look. I’d like to share one of them with you.

The Exercises in Chapter 5 of Crafting Interpreters include the following challenge for readers:

2. The Visitor pattern lets you emulate the functional style in an object-oriented language. Devise a complementary pattern for a functional language. It should let you bundle all of the operations on one type together and let you define new types easily.(SML or Haskell would be ideal for this exercise, but Scheme or another Lisp works as well.)

This Challenge Set

The design of the interpreter that readers build over the first half of this book relies heavily on the visitor pattern—so much so, that I wrote this blog post explaining what it is and why we might use it. The example emulates function-based organization in an object-oriented language. I liked the idea of having a complement to that post: a set of examples for the same domain that emulate object organization in a functionally-oriented language. That’s this post.

When I first looked at this challenge I didn’t (and still wouldn’t say I do) know either SML or Haskell, but I’ve done Lisp for programming exercises before and I wanted to try something new. So I watched this “Learn Haskell in an hour” video and picked up enough syntax try the challenge in Haskell (WARNING: I’m a professional programmer completing a specific exercise. I do not generally recommend watching a one hour syntax tutorial and then expecting to be able to ‘devise patterns’ in a language that’s new to you.)

I’ll show you the solutions I came up with below; they use the at-home birth care as the example domain, just like the Visitor pattern post did. You can play with these code samples by copying them (in full) and replacing the example code (in full) at this coderpad link.

Strap in. It’s about to get wild.

Solution #1: Loose Functions with Ad-Hoc Polymorphism

The first thing I did was:

  1. Define a data type called BirthRole with two options: Midwife and Doula, both of which accept a String as a constructor argument
  2. Declare a function for each of three at home birth types to accept a BirthRole as an argument and return a string
  3. For each of those functions, assign different behavior based on which option for BirthRole gets passed in. Both types of BirthRole accept a String, and the function declarations clarify that that String will represent the address at which the birth is taking place.
data BirthRole = Midwife String | Doula String
  deriving Show
supportLamazeBirth:: BirthRole -> String
supportLamazeBirth (Midwife address) = "Midwifing lamaze style at " ++ address
supportLamazeBirth (Doula address) = "Doula-ing lamaze style at " ++ address

supportBradleyBirth:: BirthRole -> String
supportBradleyBirth (Midwife address) = "Midwifing bradley style at " ++ address
supportBradleyBirth (Doula address) = "Doula-ing bradley style at " ++ address

supportWaterBirth:: BirthRole -> String
supportWaterBirth (Midwife address) = "Midwifing water style at " ++ address
supportWaterBirth (Doula address) = "Doula-ing water style at " ++ address

theBirth = supportWaterBirth (Doula "123 Sesame Street")

main :: IO ()
main =  print theBirth

So technically, this works. At the bottom, the variable theBirth evaluates to “Doula-ing water style at 123 Sesame Street” (look, I don’t know that much about birth logistics. I already had to do research jut to find these three styles. I didn’t want to push my luck and include inaccurate implementation details).

But this doesn’t accomplish the goals of the exercise. First of all, the operations for each type are not bundled together: they’re still organized by function.

I know this code looks like you could just move the lines around so that all the function assignments accepting the same option for the BirthRole type live together. As a matter of fact, you can’t. Go ahead, try it! Haskell expects function assignments immediately after the declaration, and it expects those assignments to be exhaustive. Mix the lines around, and it will either think you’re trying to declare the same function twice (“Multiple declarations of ‘functionName’”), or it will think you’re omitting one of the BirthRole options in your instructions about what the function should do (“Non-exhaustive patterns in function ‘functionName'”).

Second of all, if you wanted to add a type—say, a new option for BirthRole—you would have to add stuff in multiple different places. You’d need a new pipe-delimited option in the data class definition on the first line. You’d also need another line in each of the function areas for the new potential argument type. To me, that does not qualify as “letting you define new types easily.”

Third of all, it doesn’t make sense for the birth care providers to take an address in their constructor. The whole point of traveling at-home birth care providers is that their work is not summarily associated with a singular address. That’s more of a semantic concern than a technical one, but I like my code to refrain from propagating inaccurate assumptions about the domain it models as much as possible.

Let’s try again.

Solution #2: Type Classes

So it turns out Haskell has a construct to help us do this. It’s called type classes. A type class allows us to define, sort of, an interface: it serves as a blueprint for what an instance (you can think of this as ‘implementation’) should do. If an instance fails to implement any of the things defined in a Type Class that that instance purports to fit into (and there’s no default implementation either), Haskell raises (“No instance nor default method for class operation ‘functionName'”).

So my next solution:

  1. Defines a type class called BirthOperations with three functions on it, each of which accepts an instance of the type class and a string, then returns a string
  2. Declares two data classes, Midwife and Doula.
  3. Beneath each respective data class, declares the data class to be an instance of the type class BirthOperations and assigns implementations of the three functions for different at-home birth methods
class BirthOperations bOps where
  supportLamaze :: bOps -> String -> String
  supportBradley :: bOps -> String -> String
  supportWaterBirth :: bOps -> String -> String
data Midwife = Midwife 
instance BirthOperations Midwife where
  supportLamaze Midwife address = "Midwifing lamaze style at " ++ address
  supportBradley Midwife address = "Midwifing bradley style at " ++ address
  supportWaterBirth Midwife address = "Midwifing water style at " ++ address
data Doula = Doula 
instance BirthOperations Doula where
  supportLamaze Doula address = "Doula-ing lamaze style at " ++ address
  supportBradley Doula address = "Doula-ing bradley style at " ++ address
  supportWaterBirth Doula address = "Doula-ing water style at " ++ address

theBirth = supportLamaze Doula "123 Sesame Street"

main :: IO ()
main =  print theBirth

Ta-da! Once again, the variable theBirth evaluates to “Doula-ing water style at 123 Sesame Street.”

This, I think, hews closer to the goal of the challenge. All of the behavior for Midwife and Doula is grouped together. We could do the same 5 lines for another type of at-home birth care provider with ease.

But something feels off to me about this solution. It didn’t feel like ‘devising a pattern’ so much as using a language construct that Haskell already provides for free. This is supposed to be a pattern design challenge, not a “use Haskell syntax” challenge. So I decided to pretend that Haskell doesn’t provide type classes and try the exercise one more time.

Solution #3: Functions as Constructor Arguments

Lol, get a load of this.

The third solution:

  1. Defines a data type called BirthRole, and requires all options for BirthRole to have three constructor arguments. It doesn’t limit the type on these, but we’ll do that in a second. It does name them, for the sake of clarity, after the three birth methods.
  2. That data type only has one option: BirthRole. This type takes three constructor arguments, and it delineates what types those arguments must have: (String -> String) for all three. That means the type for each argument has to be a function that accepts one String argument and returns a String.
  3. Defines an ersatz interface of functions for the three birth methods. These functions, like the ones in our first solution, leverage ad hoc polymorphism, but this time we’re doing it in a much more chaotic way. Each function accepts two arguments: a BirthRole argument and a String. You’ll recall, BirthRole itself accepts three constructor arguments corresponding with the three birth methods. This interface identifies the positional argument associated with the function for its matching birth method in the BirthRole constructor, and then it calls that function on the second argument it was passed—the string. It ignores the constructor arguments associated with the other two birth methods.
  4. Below that, for each of our birth roles midwife and doula, we do a two step process:
    1. Assign implementations for three new functions that each accept a String and evaluate to a string. These will be the birth method implementations for our specific type.
    2. Declare the birth role as an instantiation of the BirthRole type, passing in the functions we made in step “a” as the constructor arguments!
  5. $$$PROFIT$$$
data BirthRole supportLamazeBirth supportBradleyBirth supportWaterBirth = BirthRole (String -> String) (String -> String) (String -> String) 
supportLamazeBirth (BirthRole lamaze _ _) address = lamaze address
supportBradleyBirth (BirthRole _ bradley _) address = bradley address
supportWaterBirth (BirthRole _ _ water) address = water address

mdwLamaze address = "Midwifing lamaze-style at " ++ address
mdwBradley address = "Midwifing bradley-style at " ++ address
mdwWater address = "Midwifing water-style at " ++ address
midWife = BirthRole mdwLamaze mdwBradley mdwWater

dlLamaze address = "Doula-ing lamaze-style at " ++ address
dlBradley address = "Doula-ing bradley-style at " ++ address
dlWater address = "Doula-ing water-style at " ++ address
doula = BirthRole dlLamaze dlBradley dlWater

theBirth = supportLamazeBirth doula "123 Sesame Street"

main :: IO ()
main =  print theBirth

Sure enough, printing theBirth gets us “Doula-ing lamaze-style at 123 Sesame Street”.

I’ll admit that the syntax here makes this solution look a little ridiculous, but I don’t think the idea is too far off from, say, a Promise callback.

Or, better yet, it comes together an awful lot like the a React component that accepts functions in its constructor. Here’s an example component constructor from the Zooniverse mobile app:

        onLeftButtonPressed={() => {
        onRightButtonPressed={() => {
        onFieldGuidePressed={() => this.classifierContainer.displayFieldGuide()}

The main difference between something like this and the Haskell “pattern” is that the arguments for functions have names in JSX (Javascript XML), and my Haskell relies on a programmer passing in the functions at the appropriate position. Haskell doesn’t support named arguments without a library.

I’ll further admit that my Haskell “pattern” does not precisely emulate objects. Object-based programming specifically accounts for instances’ ability to store state. The midWife and doula above don’t have state: they’re still, effectively, labeled bags of functions. This is fine. If the need to capture and propagate state programmatically is central to an application, Haskell (or any language devoted to being as purely functional as possible) is not the language to choose for the job. Rather, if there is value in enforcing purity (meaning ‘same input, same output’) on the types of birth care providers in our birth service system but we still need to organize them in a sorta objectish way, this is an option.

Upcoming posts on Crafting Interpreters will likely either return to the chapter-by-chapter rundowns or review changes to the interpreter itself. I hope you’ve enjoyed this little departure as much as I have!

If you liked this post, you might also like…

The rest of the Crafting Interpreters series

This post about structural verification (I’m just figuring you’re into objectcraft, so)

This post about why use, or not use, an interface (specifically in Python because of the way Python does, or rather doesn’t exactly do, interfaces)

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.