|
|
|
|
|
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. |
|
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.