| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Effable
Description
An isEffable m b
- a pure plan for the later emission of
bs - a representation of an ordered sequence of
bs, each annotated with an emission wrapperm () -> m () - emitted to its eventual result
m ()withrun(interpretation/elimination) fairly opaque
- niche: see Caveats section
Why?
Compared to just working in the monadic m b context, Effable brings one particular distinguishing feature:
An Effable can undergo decoration with emission wrappers (wrap, wrapInside) after which it is still a pure Effable.
This means that even after having been modified with wrappers, or any other supported transformation...
- ...it is still a
Functor,ApplicativeandMonadinb - ...it can be transformed further and combined into more complex structures with
<>
Emission wrappers can be applied to granular constituents of an Effable as the user code is building it. The Effable machinery will track the wrappers behind-the-scenes through all supported transformations so that the eventual emission respects them.
Caveats and usage scope
effectful predicates and emission wrappers typically run more than once when a plan is emitted
- these actions should yield the same value across evaluations, otherwise the inclusion of branches will be inconsistent
- therefore,
Effableis only suitable when these actions are read-like (return the same value over repeated evaluations and are free of externally observable side-effects)
- the nesting of combinators and use of
<*>and>>=will grow the internalEffablerepresentation, and the number of times actions are run at emission, combinatorially
Intuition
Effables are kept pure through all supported transformations by representing all possible outcomes of actions that affect structure. Running the actions is deferred to emission time. This comes with the cost of the internal representation carrying a complexity proportional to all possible outcomes.
Metaphorically, Effable is the many-worlds interpretation of actions meaning actions can be represented without them interacting with the actual world (= purity) and its representation is not collapsed to an outcome until it is observed (= run).
"Effable"?
Effable as in sayable or utterable.
Or, Eff-able as in able to be effected or effectuated - something with the potential of becoming effects.
Synopsis
- data Effable (m :: Type -> Type) b
- type Wrap (m :: Type -> Type) = m () -> m ()
- singleton :: Wrap m -> b -> Effable m b
- embed :: forall b (m :: Type -> Type). b -> Effable m b
- string :: forall b (m :: Type -> Type). IsString b => String -> Effable m b
- empty :: forall (m :: Type -> Type) b. Effable m b
- mapItems :: forall b b' (m :: Type -> Type). (b -> b') -> Effable m b -> Effable m b'
- wrap :: Wrap m -> Effable m b -> Effable m b
- wrapInside :: Wrap m -> Effable m b -> Effable m b
- when' :: Monad m => m Bool -> Effable m b -> Effable m b
- whenA :: forall (f :: Type -> Type) b. Applicative f => Bool -> Effable f b -> Effable f b
- onlyIf :: Monad m => Effable m b -> m Bool -> Effable m b
- ifThenElse :: Monad m => m Bool -> Effable m b -> Effable m b -> Effable m b
- type Enumerable a = (Enum a, Bounded a, Eq a)
- byAction :: (Monad m, Enumerable a) => m a -> (a -> Effable m b) -> Effable m b
- embedAction :: (Monad m, Enumerable a) => m a -> Effable m a
- run :: Applicative m => (b -> m ()) -> Effable m b -> m ()
- data RunWith a
- runWith :: (b -> m ()) -> Effable m b -> RunWith (m ())
Type
data Effable (m :: Type -> Type) b Source #
An ordered sequence of values, each with an associated emission wrapper (default: id).
Instances
| Alternative (Effable m) Source # | |
| Applicative (Effable m) Source # |
In the result of Examples
|
| Functor (Effable m) Source # | |
| Monad (Effable m) Source # |
The result of Note that the |
| MonadPlus (Effable m) Source # | |
| IsString b => IsString (Effable m b) Source # | |
Defined in Effable Methods fromString :: String -> Effable m b # | |
| Monoid (Effable m b) Source # | |
| Semigroup (Effable m b) Source # | |
Create
Transform items
Transform wraps
These hold for both wrap and wrapInside:
wrapfmempty==memptywrapf (x<>y) ==wrapf x<>wrapf y -- distributes over<>g<$>wrapf x ==wrapf (g<$>x) -- commutes withfmap
wrapInside :: Wrap m -> Effable m b -> Effable m b Source #
Add an additional emission wrapper to each b.
The given function composes inside of any existing wrappers.
wrapInsideid==idwrapInside(f . g) ==wrapInsideg .wrapInsidefrunemit (wrapInsidef x) ==run(f . emit) x
Branching
Conditional inclusion.
Suppress the effects of emitting the value if an effectful predicate evaluates to False:
runemit (when'(pure False) x) == pure ()runemit (when'(pure True ) x) ==runemit x
when' has the distributive and commutation properties as those of wrap.
When emitted, the monadic action will be run once for each element of the internal representation.
Flipped when'.
Arguments
| :: Monad m | |
| => m Bool | |
| -> Effable m b | to include if True |
| -> Effable m b | to include if False |
| -> Effable m b |
Binary choice.
When emitted, the monadic action will be run twice for each element of the internal representation.
Arguments
| :: (Monad m, Enumerable a) | |
| => m a | monadic evaluation point |
| -> (a -> Effable m b) | the function to evaluate |
| -> Effable m b |
Evaluation of finite-domain function for the daring.
When emitted, the monadic action will be run once for each element, for each inhabitant of the Enumerable type that the action yields.
Comparing with >>=
The signature can be compared to that of monadic bind (>>=):
byAction:: (Enumerablea, e b ~Effablem b) =>Monadm => m a -> (a -> e b) -> e b (>>=) ::Monadm => m a -> (a -> m b) -> m b
A possible interpretation of the two signatures is:
>>=returns a value-yielding action that may depend on what value some other action yieldsbyActionreturns a pureEffablethat may depend on what value some action yields
Warning: should only be used with types of a few inhabitants
All outcomes of the action are reified in the internal representation. With IO-like monads, there is no short-circuiting; neither will we be saved by laziness - the full internal representation is likely to be forced at emission time.
Bool has two inhabitants so reifying IO Bool is not expensive.
--- GHCi session --- λ> effable = byAction (pure True) embed λ> -- make GHCi report evaluation time and allocated bytes: λ> :set +s λ> run print effable True (ran for 0.01 secs, allocated 1,014,600 bytes)
An Int is Enumerable but has many inhabitants so reifying IO Int becomes costly in both time and allocations.
--- GHCi session --- λ> ineffable = byAction (pure (1::Int)) embed λ> :set +s λ> run print ineffable 1 (ran for 1.02 million years, allocated 3.06e10 gigabytes)
Arguments
| :: (Monad m, Enumerable a) | |
| => m a | monadic value |
| -> Effable m a |
Evaluation of finite-domain value for the daring.
When emitted, the monadic action will be run once for each value of the domain.
(The same warning as that for byAction applies.)
Effectuate
Arguments
| :: Applicative m | |
| => (b -> m ()) | emitting one |
| -> Effable m b | |
| -> m () |
For each b of an Effable, emit it with the given function, then apply the composed emission wrapper associated with that b, and combine all results.
Laws
Examples
These examples use whose emission effect is to accumulate emitted items into a list.Const []
>>>import Data.Functor.Const (Const (..))>>>emitConst b = Const [b]
>>>run emitConst (embed 'a' <> embed 'b')Const "ab"
With emission wrapper:
>>>silence = wrap (\_ -> Const [])>>>run emitConst (embed 'a' <> (silence $ embed 'b') <> embed 'c')Const "ac"
Instances
| Foldable RunWith Source # | |
Defined in Effable Methods fold :: Monoid m => RunWith m -> m # foldMap :: Monoid m => (a -> m) -> RunWith a -> m # foldMap' :: Monoid m => (a -> m) -> RunWith a -> m # foldr :: (a -> b -> b) -> b -> RunWith a -> b # foldr' :: (a -> b -> b) -> b -> RunWith a -> b # foldl :: (b -> a -> b) -> b -> RunWith a -> b # foldl' :: (b -> a -> b) -> b -> RunWith a -> b # foldr1 :: (a -> a -> a) -> RunWith a -> a # foldl1 :: (a -> a -> a) -> RunWith a -> a # elem :: Eq a => a -> RunWith a -> Bool # maximum :: Ord a => RunWith a -> a # minimum :: Ord a => RunWith a -> a # | |
| Traversable RunWith Source # | |
| Functor RunWith Source # | |
Create a representation of the individual emission results of an Effable.
Methods of the Foldable and Traversable instances of the result type can be used e.g. to customize how the individual emission results are combined.
sequenceA_(runWithemit x) ==runemit x
Example
The following uses the foldr method of RunWith's Foldable instance to create a run-like function with a foldr-style API:
>>>:{let run_foldr :: (b -> m ()) -> (m () -> c -> c) -> c -> Effable m b -> c run_foldr emit comb z = foldr comb z . runWith emit :}