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)