Hacker News new | ask | show | jobs
by NM-Super 1311 days ago
This article is from 2017. In the meantime, the Haskell community has been playing around a lot with Effect Systems, which give you similar benefits with much better performance and generally less boilerplate. In particular, the effectful package (https://hackage.haskell.org/package/effectful) would let you write the code in the article as:

  reserve :: (SearchTrains :> es, GetTypology :> es, Log :> es, RequestReservation :> es) => ReservationRequest -> Eff es ReservationResult
  reserve request = do
    trains <- send (SearchTrain (_dateTime request)) -- Search for trains at date-time
    forM trains $ \train ->                   -- Loop on all the trains
      typology <- send (GetTypology train)           -- Get the typology of a train
      ...                                     -- Implement the reservation rules
      send (Log "Confirming reservation")          
      confirmed <- send (RequestReservation reservation)
      ...
(In actual usage you won't tend to use the send function - you'll write wrappers that do it for you).

This lets you avoid having to write an interpreter for some particular ReservationExpr monad - instead, you specify individual effect handlers, then use them as you want. For example, we could write something like this:

  -- Make search train never return any trains
  runWithoutTrains :: Eff (SearchTrains ': es) a -> Eff es a
  runWithoutTrains = interpret $ \_unusedHere (SearchTrain name) -> pure []

  ignoreLogs :: Eff (Log ': es) a -> Eff es a
  ignoreLogs = interpret $ \_unusedHere (Log _message) -> pure ()

What's even more fun is that you can actually interpose effects. So, I can write this:

  loggingTrainSearches :: (Log :> es, SearchTrain :> es) => Eff es a -> Eff es a
  loggingTrainSearches = interpose $ \_unusedHere (SearchTrain name) -> do
    send (Log ("Searching for train " <> show name))
    send (SearchTrain name)

Being able to play around with handlers like this is really useful, both for testing and for adding on niceties to business logic.
2 comments

I had read this article more as a "from-first-principles" example, but it's a good note for people who might be unfamiliar with practical Haskell. Using an effects system also promotes the domain DSL from an initial-tagless language (only the interpreter is extensible) to a tagless-final language (both the language and the interpreter are extensible).

It may be worth noting that effectful, polysemy, and the other "new kids" of effects systems are not the only option: record-of-functions, mtl, and type-class effects aren't cutting edge, but they're a little more approachable from an OOP dependency injection pattern.

Year added above. Thanks!