packages feed

spectacle-1.0.0: src/Language/Spectacle/Lang.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}

-- | The 'Lang' monad and functions for defining Spectacles syntax as effects.
--
-- @since 1.0.0
module Language.Spectacle.Lang
  ( -- * Lang
    Lang (Pure, Op, Scoped),
    runLang,
    send,
    scope,
    weaken,

    -- * Effects
    type EffectK,
    type ScopeK,
    FirstOrder,
    Effect,

    -- ** Membership
    type Members,
    Member (inject, project, injectS, projectS),

    -- ** Unions
    Op (OHere, OThere),
    Scoped (SHere, SThere),
    decomposeOp,
    extractOp,
    decomposeS,
    extractS,
  )
where

import Data.Functor.Loom (hoist, (~>~))
import Language.Spectacle.Lang.Internal (Lang (Op, Pure, Scoped), scope, send)
import Language.Spectacle.Lang.Member (Member (inject, injectS, project, projectS), type Members)
import Language.Spectacle.Lang.Op (Op (OHere, OThere), decomposeOp, extractOp)
import Language.Spectacle.Lang.Scoped
  ( Effect,
    EffectK,
    FirstOrder,
    ScopeK,
    Scoped (SHere, SThere),
    decomposeS,
    extractS,
  )

-- ---------------------------------------------------------------------------------------------------------------------

-- | Used to unwrap the pure value in 'Lang' after all of its effects have been discharged.
--
-- @since 1.0.0
runLang :: Lang ctx '[] a -> a
runLang (Pure x) = x
runLang _ =
  -- @Lang ctx '[] a@ can only be constructed with 'Pure' or obtained by discharging all its
  -- effects, which would result in 'Pure'. Cases where @Lang ctx '[] a@ holds impure values mean
  -- that:
  --
  -- 1. An effect escaped the scope of 'Lang' and therefore was not discharged when the handler for
  -- that effect was run on 'Lang'. This not impossible but is /very/ difficult to do since the
  -- escaped effect would have to be hidden from 'Loom'. 'Lang' in a first-order operation,
  -- FO effects with resumptions to Lang, or intentionally weakening/coercing a @Lang ctx effs' a@
  -- into some other 'Lang' are all ways which basically guarantee that effects will be left
  -- unhandled.
  --
  -- 2. Operations like 'unsafeCoerce' were used to change the effect signature of 'Lang'.
  error
    "internal error: Lang match against Yield, this means that an effect escaped the scope of Lang \
    \and was left unhandled. This should be impossible."

-- | Appends an effect label @eff@ to the head of a 'Lang's effect signature by the weakening rule
-- for sum types.
--
-- @since 1.0.0
weaken :: forall eff effs ctx a. Lang ctx effs a -> Lang ctx (eff ': effs) a
weaken = \case
  Pure x -> pure x
  Op op k -> Op (OThere op) (weaken . k)
  Scoped scoped loom -> Scoped (SThere scoped) (loom ~>~ hoist weaken)