Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Using Relative Monads for Cheap Syntax

Using Relative Monads for Cheap Syntax

This presentation shows a technique based on relative monads that allows one to embed generic syntax involving a monad, m, which can then be simply reused for any relative monad, relative to m.

It shows a simplified example of a production use of this at CBA (converted to Haskell), which has allowed us to cheaply add error handling to new monads and the theory behind it. It also compares this approach with monad transformers/mtl.

This presentation was delivered at the Yow! Lambda Jam conference in Brisbane, Australia in May, 2015.

788a9ad3374370466f62f8003abad77b?s=128

Stephan Hoermann

May 21, 2015
Tweet

Transcript

  1. Cheap Syntax using Relative Monads Rowan Davies & Stephan Hoermann

    CBA
  2. Result data Result a = Success a | Failure String

  3. Result API setMessage result or getOrElse

  4. File System Library data FS a = FS { runFS

    :: FilePath -> IO (Result a) }
  5. FS API mapResult :: (Result a -> Result b) ->

    FS a -> FS b mapResult f fs = FS $ \cwd -> f <$> (runFS fs cwd) setMessage :: String -> FS a -> FS a setMessage msg = mapResult (R.setMessage msg)
  6. FS API or finally bracket onException addMessage setMessage recover

  7. Can’t have too much of a good thing data Hive

    a = Hive { runHive :: HiveClient -> Result a }
  8. Hive API mapResult :: (Result a -> Result b) ->

    Hive a -> Hive b mapResult f hive = Hive $ \client -> f (runHive hive client) setMessage :: String -> Hive a -> Hive a setMessage msg = mapResult (R.setMessage msg)
  9. FS API mapResult :: (Result a -> Result b) ->

    FS a -> FS b mapResult f fs = FS $ \cwd -> f <$> (runFS fs cwd) setMessage :: String -> FS a -> FS a setMessage msg = mapResult (R.setMessage msg)
  10. Hive API or finally bracket onException addMessage setMessage recover

  11. None
  12. Why not monad transformers? Why not monad error?

  13. Relative Monad class RelMonad m r where retRel :: m

    a -> r a (>%=) :: r a -> (m a -> m (r b)) -> r b
  14. FS ~ Result instance RelMonad Result FS where retRel ::

    Result a -> FS a retRel r = FS (const.return $ r) (>%=) :: FS a -> (Result a -> Result (FS b)) -> FS b fs >%= f = FS $ \cwd -> let applied = f <$> runFS fs cwd in applied >>= result (\s -> runFS s cwd) (return . Failure)
  15. Higher level abstractions rMap :: (Monad m, RelMonad m r)

    => (m a -> m b) -> r a -> r b rMap f r = r >%= \x -> return (retRel (f x)) rSetMessage :: RelMonad Result r => String -> r a -> r a rSetMessage msg = rMap (setMessage msg)
  16. In Scala trait RelMonad[M[_], R[_]]{ def rPoint[A](v: => M[A]): R[A]

    def rBind[A, B] (ma: R[A]) (f: M[A] => M[R[B]]): R[B] }
  17. So why not monad error? • Different trade offs •

    rMap • Derive Monad/Monad plus for all R.
  18. The Theory

  19. General (non-relative) Monads Definition: A monad over category C comprises:

    [Typed functional terminology] [Like a type operator R  ::  *  -­‐>  *  ] For each type [Analogous to return  ::  a  -­‐>  R  a  ] 
 For each types [Analogous to >>=  ::  R  a  -­‐>  (a  -­‐>  R  b)  -­‐>  R  b ] 
 
 [Here a category is roughly “a language with types and composable mappings between them”.]
  20. General Relative Monads Definition: A relative monad over categories C

    and D comprises: a functor M : C→D (a well-typed translation from C to D) an type mapping R : Ctype → Dtype (a translation from C types to D types) for each type X in C, a map in D unitX : M X → R X (analogous to return) 
 for each X, Y types in C extendX,Y : (M X → R Y ) → (R X → R Y ) (analogous to >>= :) Monads Need Not Be Endofunctors 
 [Altenkirch, Chapman And Uustalu 2010]

  21. Simple Relative Monads Our simple form of relative monads has

    D=C - so just one category. – a functor M : C→C (maps types & terms back to the same language) – an type mapping R : Ctype → Ctype (a type operator) – for each type X in C, a map in D unitX : M X → R X (analogous to return) 
 – for each X, Y types in C extendX,Y : (M X → R Y ) → (R X → R Y ) (analogous to >= :) Our relative monads are endofunctors (“relative endo-monads?”)
 But they still involve M, unlike standard monads. 

  22. Simple Relative Monads Our programming relative monads have D=C =

    “Haskell/Scala types & typed functions”
 For Haskell: a monad Monad m (We’ll focus on when m is a monad) a type operator r :: * → * a polymorphic function retRel :: forall x. m x → r x a polymorphic function (>%=) :: forall x y. r x → (m x → r y ) → r y Our relative monads are endofunctors.
 But they still involve m, unlike standard monads. 
 

  23. Relative Monad Laws The laws for relative monads closely follow

    the usual monad laws:
 If x::m a and k::m a ! r b then (retRel x) >%= k == k x :: r b
 If x::r a then x >%= retRel == x :: r a
 If x::r a and f::m a ! r b and g::m b ! r c then* x >%= (f >%= g) == (x >%= f) >%= g :: r c 
 [* This law also generalises to situations with relative binds for different r ’s and m’s] So, relative monads are an intuitive abstraction if you already use monads.
  24. Deriving a monad instance —- We can derive a monad

    instance for r by strengthening the —- type of >%= (It’s equivalent if we already know r is a monad.) class RelMonad m r where retRel :: m a -> r a (>%=) :: r a -> (m a -> m (r b)) -> r b --------------------------------------------------------------- type family MBase (r :: * -> *) :: * -> * instance (MBase r ~ m, Monad m, RelMonad m r) => Monad r where return = retRel . (return :: a -> m a) ra >>= f = ra >%= \ma -> (ma >>= ret.f) where ret = return :: r b -> m (r b) 
 
 —-There are equivalent alternatives, e.g., adding a “cross bind”: -- (>>%=) :: m a -> (a -> r b) -> r b 

  25. Comparison to: 
 Specialized Monad Classes data Result a =

    Success a | Failure String —- 'MonadResult r’ seems equivalent to using 'RelMonad Result r' -- but ‘RelMonad m r’ allows e.g., generalising types over ‘m’
 —- For other similar classes the relationship is less obvious.
 — class Monad m => MonadResult m where return :: a -> m a —- from Monad m raiseError :: String -> m a handleError :: (String -> m a) -> m a -> m a (=<<) :: (a -> m a) -> m a -> m a —- from Monad m setMessage :: MonadResult m => forall a. String -> m a -> m a setMessage msg = handleError (const (raiseError msg))
  26. Comparison to: Monad Transformers data ResultT m a = ResultT

    { runResultT :: m (Result a) } instance (Functor m) => Functor (ResultT m) where fmap f rt = ResultT $ fmap (fmap f) (runResultT rt) instance (Monad m) => Monad (ResultT m) where return = lift . return rt >>= f = . . . (omitted)
 instance MonadTrans ResultT where lift = ResultT . liftM Success setMessageT :: (Functor m) => String -> ResultT m a -> ResultT m a setMessageT msg rt = ResultT $ setMessage msg <$> (runResultT rt) • While there’s overlap in use and purpose:
 - transformers firstly build monads, and may also relate them
 - RelMonads relate first, and then may also derive monad instances.
 You can use both, depending on what you need to build and/or relate. • retRel is a lift/monad morphism (cf. the Haskell package, mmorph)
 >%= is computationally important for going “the other way
  27. Conclusion • Our relative monads arose in pragmatic refactoring. •

    They are natural and often easy to implement. • They are a simple, well-understood case of a recent theoretical concept. [Altenkirch, Chapman And Uustalu 2010] • Their laws are intuitive if you already use monads. • retRel with >%= makes coding “between monads” very natural. • Come to the workshop - the details are compelling.
  28. Workshop • Scala or Haskell • https://github.com/CommBank/lambdajam-relative-monads • Relative monad

    and monad transformers/mtl
 
 • Also see the production code:
 https://github.com/CommBank/omnitool