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 fcApplicative 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 -> defaultActionApplicative 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
Applicatives anyway!
(>>=) with IxApplicative and Extensible
RecordRequired Types etc:
IxApplicativeIxStateT
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 IxApplicativeIxApplicative 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) 1Demonstrate 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