Yuji Yamamoto (山本悠滋)
2019-08-23 HIW 2019
Monad
can do but Applicative
cannot,
and vice versa.(>>=)
with IxState
and
keep your EDSL Monad-free.
Symbol
.IxState
with an extensible
record when executing.Monad
Can Do But Applicative
Cannot>>=
Applicative
Can Do But Monad
CannotSeveral things. Today I focus on:
Applicative
Can Do But Monad
CannotWhy?
do
x <- fa
-- `x` can be used to decide whether `fb` or `fc` is executed.
-- But your EDSL interpreter (e.g. Free Monad) doesn't know
-- *both* `fb` and `fc` can be executed.
y <- if x >= 0 then fb else fc
Applicative
Can Do But Monad
CannotWhy?
do
x <- fa
-- Actions following `fa` can be decided dynamically by `x`.
-- So your EDSL interpreter (e.g. Free Monad) has
-- completely no idea of what's executed next.
-- nextActionsTable :: [(x, m ())]
case lookup x nextActionsTable of
Just nextAction -> nextAction
Nothing -> defaultAction
Applicative
Can Do But Monad
CannotWhy?
Monad
can choose which execution paths to go by the
results of the actions, thanks to >>=
.Monad
can’t grasp all
possibly-executed actions.
Applicative
can do that!Applicative
Can Do But Monad
CannotTraversing all paths is useful for…
--help
of the all available options
by traversing them.I want to add some bind-like feature to those
Applicative
s anyway!
(>>=)
with IxApplicative and Extensible
RecordRequired Types etc:
IxApplicative
IxStateT
if you write a monadic interpreter for the EDSL.RebindableSyntax
if you make the EDSL available with
do
syntax.(>>=)
with IxApplicative and Extensible
RecordExample: Not very useful expression language.
data Exp a where
Hask :: a -> Exp a
-- ^ Literal value.
Succ :: Exp Int -> Exp Int
-- ^ `succ` function.
(:<) :: Exp Int -> Exp Int -> Exp Bool
-- ^ Comparison operator.
(>>=)
with IxApplicative and Extensible
RecordAdd value constructors for Applicative
instance.
data Exp a where
-- ...
Fmap :: (a -> b) -> Exp a -> Exp b
-- ^ For `fmap`
Ap :: Exp (a -> b) -> Exp a -> Exp b
-- ^ For `<*>`
Then :: Exp a -> Exp b -> Exp b
-- ^ For `*>`
(>>=)
with IxApplicative and Extensible
RecordAdd extensible records as type arguments:
data Exp (xs :: [Assoc Symbol Type]) (ys :: [Assoc Symbol Type]) a where
Hask :: a -> Exp xs xs a
Succ :: Exp xs xs Int -> Exp xs xs Int
(:<) :: Exp xs xs Int -> Exp xs xs Int -> Exp xs xs Bool
Fmap :: (a -> b) -> Exp xs ys a -> Exp xs ys b
Ap :: Exp xs ys (a -> b) -> Exp ys zs a -> Exp xs zs b
Then :: Exp xs ys a -> Exp ys zs b -> Exp xs zs b
(>>=)
with IxApplicative and Extensible
RecordAdd extensible records as type arguments:
Exp
is an IxApplicative
IxApplicative
and IxFunctor
.Fmap :: (a -> b) -> Exp xs ys a -> Exp xs ys b
Ap :: Exp xs ys (a -> b) -> Exp ys zs a -> Exp xs zs b
Then :: Exp xs ys a -> Exp ys zs b -> Exp xs zs b
(>>=)
with IxApplicative and Extensible
RecordAdd operators to update associated extensible records:
data Exp (xs :: [Assoc Symbol Type]) (ys :: [Assoc Symbol Type]) a where
-- ...
Let :: KnownSymbol k =>
FieldName k -> Exp xs xs a -> Exp xs (k >: a ': xs) ()
-- ^ Introduce a new variable.
Ref :: (Lookup xs k v, KnownSymbol k) =>
FieldName k -> Exp xs xs v
-- ^ Read an already introduced variable.
(:=) :: (Lookup xs k v, KnownSymbol k) =>
FieldName k -> Exp xs xs v -> Exp xs xs ()
-- ^ Update an already introduced variable.
(>>=)
with IxApplicative and Extensible
RecordAdd more operators to refer the bound variables.
data Exp (xs :: [Assoc Symbol Type]) (ys :: [Assoc Symbol Type]) a where
-- ...
If :: Exp xs xs Bool -> Exp xs ys a -> Exp xs ys a -> Exp xs ys a
While :: Exp xs xs Bool -> Exp xs xs () -> Exp xs xs ()
Now I turned a tiny expression EDSL into an imperative EDSL which can update variables.
>>=
operator! Let #var (Hask 0)
*>> Let #var2 (Hask (1 :: Int))
*>> While (Ref #var :< Hask 10)
( (#var := Succ (Ref #var))
*>> If (Ref #var :< Hask (5 :: Int))
(#var2 := Succ (Ref #var2))
(ireturn ())
)
Bonus: using RebindableSyntax
Let #var 0
Let #var2 1
While (Ref #var :< 10) $ do
#var := Succ (Ref #var)
If (Ref #var :< 5)
(#var2 := Succ (Ref #var2))
(ireturn ())
run :: Exp '[] ys a -> (a, Record ys)
run = (`runIxState` nil) . toIxState
where
-- ...
toIxState (Let k mx) = toIxState mx >>>= \x -> imodify (k @== x <:)
toIxState (Ref k) = igets (^. itemAssoc (fieldNameToProxy k))
toIxState (k := mx) =
toIxState mx >>>= imodify . set (itemAssoc (fieldNameToProxy k))
toIxState (If cond t f) =
toIxState cond >>>= bool (toIxState f) (toIxState t)
-- ...
Count how many times every variable is referred.
collectStats :: Exp xs ys a -> Map.Map String Int
-- ...
collectStats (Let _ mx) = collectStats mx
collectStats (Ref k) = one k
collectStats (k := mx) = one k `merge` collectStats mx
collectStats (If cond t f) =
collectStats cond `merge` collectStats t `merge` collectStats f
-- ...
merge = Map.unionWith (+)
one k = Map.singleton (symbolVal $ fieldNameToProxy k) 1
Demonstrate here!
I failed to implement in time…😞
IxApplicative
, we can add a bind-like feature to
our DSLs.Space, Right Arrow or swipe left to move to next slide, click help below for more details