Chris Martin

Java interfaces map to Haskell records

Gabriel Gonzalez recently wrote an article entitled Advice for Haskell beginners and I was mildly surprised to see that out of four tips, one was:

Avoid typeclass abuse

On second thought, though, this is important advice. In retrospect, my inclination to use typeclasses in inappropriate ways was a big problem in my earlier Haskell days. I made this mistake a lot:

I thought Java interfaces mapped to Haskell typeclasses.

But really, more often they map to Haskell records.

Julie Moronuki and I have written fairly extensively about the extent to which interfaces and typeclasses can be compared. (Summary of our conclusion: they share a related conceptual purpose but are not directly analogous.) Here I want to share a quick example of how they do map to records, by translating a Java example into Haskell.

To pick a simple and arbitrary example, I went to the Oracle's Java documentation page entitled What Is an Interface?. They give the following Bicycle interface:

interface Bicycle {
    void changeCadence(int newValue);
    void changeGear(int newValue);
    void speedUp(int increment);
    void applyBrakes(int decrement);
}

And then a class called AcmeBicycle, which implements Bicycle and also has an additional method called printStates.

class AcmeBicycle implements Bicycle {

    int cadence = 0;
    int speed = 0;
    int gear = 1;

    void changeCadence(int newValue) {
         cadence = newValue;
    }

    void changeGear(int newValue) {
         gear = newValue;
    }

    void speedUp(int increment) {
         speed = speed + increment;
    }

    void applyBrakes(int decrement) {
         speed = speed - decrement;
    }

    void printStates() {
         System.out.println("cadence:" +
             cadence + " speed:" +
             speed + " gear:" + gear);
    }
}

So now let's turn that into Haskell. I'm going to translate as directly as possible. Using mutable references for the three fields of AcmeBicycle is unusual for Haskell, but for the sake of comparison we'll do it anyway using three IORefs.

The preliminaries: Let's enable RecordWildCards because we'll be using records a lot, and import the module containing IORef.

{-# LANGUAGE RecordWildCards #-}

import Data.IORef

Now we'll jump straight to the punchline: Here's how the Bicycle Java interface is represented as a Haskell record. Each of the four methods in the interface corresponds to a field in the record.

data Bicycle =
  Bicycle
    { changeCadence :: Int -> IO ()
    , changeGear    :: Int -> IO ()
    , speedUp       :: Int -> IO ()
    , applyBrakes   :: Int -> IO ()
    }

The AcmeBicycle Java class also translates into a Haskell record. Each of the three fields in the Java interface corresponds to a field in the record.

data AcmeBicycle =
  AcmeBicycle
    { cadence :: IORef Int
    , speed   :: IORef Int
    , gear    :: IORef Int
    }

The implicit constructor in the Java class becomes an explicit IO action in Haskell.

newAcmeBicycle :: IO AcmeBicycle
newAcmeBicycle =
  do
    cadence <- newIORef 0
    speed   <- newIORef 0
    gear    <- newIORef 1
    return AcmeBicycle{..}

The printStates Java class method becomes an ordinary top-level Haskell function.

printStates :: AcmeBicycle -> IO ()
printStates AcmeBicycle{..} =
  do
    c <- readIORef cadence
    s <- readIORef speed
    g <- readIORef gear
    putStrLn ("cadence:" ++ show c ++
              " speed:" ++ show s ++
              " gear:" ++ show g)

In the Java example, AcmeBicycle is a subtype of Bicycle, which in Java parlance means that every AcmeBicycle value is also a Bicycle value. Thus if x is an AcmeBicycle and f is a function that accepts a Bicycle argument, we can write the Java expression f(x).

We don't have that kind of polymorphism in our Haskell translation, but we really don't need it; we just need a function that converts AcmeBicycle to Bicycle.

acmeToBicycle :: AcmeBicycle -> Bicycle
acmeToBicycle AcmeBicycle{..} =
  Bicycle
    { changeCadence = writeIORef cadence
    , changeGear    = writeIORef gear
    , speedUp       = \x -> modifyIORef' speed (\s -> s + x)
    , applyBrakes   = \x -> modifyIORef' speed (\s -> s - x)
    }

Thus if x is an AcmeBicycle and f is a function that accepts a Bicycle argument, we can write the Haskell expression f (acmeToBicycle x). There is one extra function call involved, but in exchange we eliminate the conceptual complexity of Java subtyping or Haskell overloading, and we can program entirely in simple monomorphic functions and data types.