"sum-like" versus "product-like" effect signatures

In effect systems like effectful or fused-effects, effects are defined as sum types:

data FileSystem :: Effect where
  ReadFile  :: FilePath -> FileSystem m String
  WriteFile :: FilePath -> String -> FileSystem m ()

And there’s a typeclass like :> or Member that says “this effect is part of the whole set of effects”.

Meanwhile, when using the so-called “ReaderT pattern” (a.k.a. “handle pattern”), components are records-of-functions like

data FileSystem = FileSystem {
     readFile  :: FilePath -> m String
     writeFile :: FilePath -> String -> m ()
  }

And (in some formulations) there’s a typeclass like Has that says “this component is part of the whole set of components”.

I personally prefer the records-of-functions + Has approach because it’s easier to reuse intuitions coming from object-oriented programming.

What are the benefits of the sum-of-constructors + Member approach?

The sum approach reuses intuitions coming from algebraic-data-type-oriented programming. :smiley:

The real beauty though is that there is no difference other than personal habit/POV/ideology/what-have-you. The following two types are equivalent:

Either X Y -> C
(X -> C, Y -> C)

This is why you can code up dependency injection either way.

6 Likes

I’ve learned from the paper Effect Handlers in Haskell, Evidently mentioned in other thread that some effect libraries do use records instead of GADTs with multiple constructors for defining effects:

• The library interface […] is concise and arguably
simpler than other library interfaces for effect handlers.
In particular, effects are defined as a regular data type
with a field for each operation.

Other libraries typically require GADT’s [Kiselyov and
Ishii 2015], data types à la carte [Swierstra 2008; Wu
et al. 2014], or Template Haskell [Kammar et al. 2013]
to create new effects.

And they also use record field selectors when invoking operations, which actually seems quite Haskelly and ergonomic:

To perform operations, we pass the operation selector ask
with its argument to the perform function.