packages feed

free-alacarte-0.1.0.2: src/Free/AlaCarte.hs

{-# LANGUAGE LambdaCase #-}
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
{-# OPTIONS_GHC -fno-warn-missing-export-lists #-}
{-# OPTIONS_GHC -fno-warn-all-missed-specialisations #-}

-- Inspired on Data types a la carte

module Free.AlaCarte where

import Data.Kind

infixr 8 :+:

type (:+:) :: forall {k}. (k -> Type) -> (k -> Type) -> k -> Type
data (f :+: g) e = Left' (f e) | Right' (g e) deriving (Functor)

type (:<:) :: (Type -> Type) -> (Type -> Type) -> Constraint
class (Functor sub, Functor sup) => sub :<: sup where
  inj :: sub a -> sup a
  prj :: sup a -> Maybe (sub a)

instance (Functor f) => f :<: f where
  {-# INLINABLE inj #-}
  inj :: f a -> f a
  inj = id

  {-# INLINABLE prj #-}
  prj :: f a -> Maybe (f a)
  prj = Just

instance (Functor f, Functor g) => f :<: (f :+: g) where
  {-# INLINABLE inj #-}
  inj :: f a -> (f :+: g) a
  inj = Left'

  {-# INLINABLE prj #-}
  prj :: (f :+: g) a -> Maybe (f a)
  prj = \case
    Left' e -> Just e
    Right' _e -> Nothing

instance {-# OVERLAPPABLE #-} (Functor f, Functor g, f :<: g, Functor h) => f :<: (h :+: g) where
  {-# INLINABLE inj #-}
  inj :: f a -> (h :+: g) a
  inj = Right' . inj

  {-# INLINABLE prj #-}
  prj :: (h :+: g) a -> Maybe (f a)
  prj = \case
    Left' _e -> Nothing
    Right' e -> prj e

type Free :: (Type -> Type) -> Type -> Type
data Free f a
  = Pure a
  | Impure (f (Free f a))
  deriving (Functor)

instance (Functor f) => Applicative (Free f) where
  {-# INLINABLE pure #-}
  pure :: a -> Free f a
  pure = Pure

  {-# INLINABLE (<*>) #-}
  (<*>) :: Free f (a -> b) -> Free f a -> Free f b
  Pure f <*> t = fmap f t
  Impure f <*> t = Impure (fmap (<*> t) f)

instance (Functor f) => Monad (Free f) where
  {-# INLINABLE (>>=) #-}
  (>>=) :: Free f a -> (a -> Free f b) -> Free f b
  Pure x >>= f = f x
  Impure t >>= f = Impure (fmap (>>= f) t)

foldFree ::
  forall f a b.
  (Functor f) =>
  (a -> b) ->
  (f b -> b) ->
  Free f a ->
  b
foldFree pure' impure = go
  where
    go :: Free f a -> b
    go = \case
      Pure x -> pure' x
      Impure t -> impure (fmap go t)

exec :: (Exec f) => Free f a -> IO a
exec = foldFree return execAlgebra

instance (Exec f, Exec g) => Exec (f :+: g) where
  {-# INLINABLE execAlgebra #-}
  execAlgebra = \case
    Left' e -> execAlgebra e
    Right' e -> execAlgebra e

type Exec :: (Type -> Type) -> Constraint
class (Functor f) => Exec f where
  execAlgebra :: f (IO a) -> IO a

injectFree :: (g :<: f) => g (Free f a) -> Free f a
injectFree = Impure . inj