can-i-haz-0.2.1.0: Generic implementation of the Has and CoHas patterns

Stabilityexperimental
Safe HaskellSafe
LanguageHaskell2010

Control.Monad.Except.CoHas

Description

This module defines a class CoHas intended to be used with the MonadError class (and similar ones) or 'Control.Monad.Except.Except'/'Control.Monad.ExceptT' types.

The problem

Assume there are several types representing the possible errors in different parts of an application:

data DbError = ...
data WebUIError = ...

as well as a single sum type containing all of those:

data AppError
  = AppDbError DbError
  | AppWebUIError WebUIError

What should be the MonadError constraint of the DB module and web module respectively?

  1. It could be MonadError AppError m for both, introducing unnecessary coupling.
  2. Or it could be MonadError DbError m for the DB module and MonadError WebError m for the web module respectively, but combining them becomes a pain.

Or, it could be MonadError e m, CoHas AppError e for the DB module (and similarly for the web module), where some appropriately defined CoHas option sum class allows injecting option creating a value of the sum type. This approach keeps both modules decoupled, while allowing using them in the same monad stack.

The only downside is that now one has to define the CoHas class and write tedious instances for the AppError type (and potentially other types in case of, for example, tests).

But why bother doing the work that the machine will happily do for you?

The solution

This module defines the generic CoHas class as well as hides all the boilerplate behind GHC.Generics, so all you have to do is to add the corresponding deriving-clause:

data AppError
  = AppDbError DbError
  | AppWebUIError WebUIError
  deriving (Generic, CoHas DbError, CoHas WebUIError)

and use throwError . inject instead of throwError (but this is something you'd have to do anyway).

Type safety

What should happen if sum does not have any way to construct it from option at all? Of course, this means that we cannot inject option into sum, and no CoHas instance can be derived at all. Indeed, this library will refuse to generate an instance in this case.

On the other hand, what should happen if sum contains multiple values of type option (like Either option option), perhaps on different levels of nesting? While technically we could make an arbitrary choice, like taking the first one in breadth-first or depth-first order, we instead decide that such a choice is inherently ambiguous, so this library will refuse to generate an instance in this case as well.

Synopsis

Documentation

class CoHas option sum where Source #

The CoHas option sum class is used for sum types that could be created from a value of type option.

Minimal complete definition

Nothing

Methods

inject :: option -> sum Source #

Inject an option into the sum type.

The default implementation searches sum for some constructor that's compatible with option and creates sum using that constructor. The default implementation typechecks iff there is a single matching constructor.

inject :: forall path. (Generic sum, SuccessfulSearch option sum path) => option -> sum Source #

Inject an option into the sum type.

The default implementation searches sum for some constructor that's compatible with option and creates sum using that constructor. The default implementation typechecks iff there is a single matching constructor.

Instances
CoHas sum sum Source #

Each type can be injected into itself (and that is an id injection).

Instance details

Defined in Control.Monad.Except.CoHas

Methods

inject :: sum -> sum Source #

SuccessfulSearch a (Either l r) path => CoHas a (Either l r) Source # 
Instance details

Defined in Control.Monad.Except.CoHas

Methods

inject :: a -> Either l r Source #

type SuccessfulSearch option sum path = (Search option (Rep sum) ~ Found path, GCoHas path option (Rep sum)) Source #

Type alias representing that the search of option in sum has been successful.

The path is used to guide the default generic implementation of CoHas.