Four ways of declaring interfaces in Haskell

I wrote another blog post on several ways to declare interfaces in Haskell

I’d be curious to know if people actually use other ways to define interfaces

12 Likes

I really didn’t know about the GADT one, i guess it is a fine example of the utility of rank-N types. Beyond that i really think more attention should be given that User, UserID, UserName and Birthday are also part of the interface.
Actually come to think of it, assuming i have a type named DepType from my dependency how could i even declare a interfaceMethod which stands for depMethod?. this seems extremely difficult, the naive approach of declaring InterfaceType = MkInterfaceType DepType obviously needs to compile and link against whatever library contains DepType.

No mention of backpack :cry: (not that I’m actually using that in anger)

4 Likes

I have another blog post where I go more into details about how to decouple an interface from its implementation(s): Decoupling from dependencies

Unfortunately I’m not really familiar with it. Thanks for the mention, I’ll take a look

I love how the GADT can capture the return type of each command without callbacks. Is there a general translation pattern between GADTs and Functors-with-callbacks? I.e. can we always translate a constructor
C :: a -> b -> GADT r to an ordinary constructor C :: a -> b -> (r -> x) -> F x of the functorial interface?

The first approach subsumes the others:

class UserRepositoryClass m where
  userRepositoryRecord :: UserRepositoryRecord m
  interpretGADT :: UserRepositoryGADT a -> m a
  freeInterpreter :: UserRepositoryFree a -> m a

Further, due to the isomorphism

Either a b -> r  ~  (a -> r, b -> r)

consuming an enumeration of interface operations is essentially the same as putting all consumers into a record. That is roughly the link between way 2 and way 3.

What I found most challenging in real applications is to define the boundary where the commands to the interface enter the program. That is, define an appropriate sum-of-products type that can be used to separate the business logic from a command line interface as well as a web form. Should the commands record be whatever optparse-applicative generates? Or do we set the boundary to a stage where some pre-processing and expansion has already happened? Should any static configuration that is loaded at startup, before commands are accepted, be part of the interface type? Or should it be a reader monad component of the interface-interpreting monad?

2 Likes

Yes, this translation is always possible. In fact, it corresponds to composing the original GADTs into the following data type, known as Coyoneda:

data Coyoneda f x = forall r. Coyoneda (f r) (r -> x)

The functor Coyoneda C, obtained by substituting C for f, represents all of the constructors of C in the functors-with-callbacks form.

(By the way, defining the type type Freer f = Free (Coyoneda f) yields a free monad that can work directly with raw GADTs, without requiring them to be rewritten in the functor-with-callbacks style. This construction is known as the Freer monad.)

3 Likes

What I found most challenging in real applications is to define the boundary where the commands to the interface enter the program. That is, define an appropriate sum-of-products type that can be used to separate the business logic from a command line interface as well as a web form. Should the commands record be whatever optparse-applicative generates? Or do we set the boundary to a stage where some pre-processing and expansion has already happened? Should any static configuration that is loaded at startup, before commands are accepted, be part of the interface type? Or should it be a reader monad component of the interface-interpreting monad?

Personally, I’d try to follow as much as possible the principles of Domain Driven Design, where Commands are part of the Domain and should therefore use the domain language. This also means that implementation details should be hidden and decoupled away, probably having a layer to interpret into the domain language the concrete commands received from a cli or a web interface.

2 Likes