Alexander Granin
graninas@gmail.com
eDSL for transition graph using Free
monads and existential types
Structure
● The problem
● Embedded DSLs design
● Transition Graph eDSL
● Sample application
● Implementation
● Why you shouldn’t write such code
data FlowMethodF a s
= RunUI (Interaction (UIResult s)) (UIResult s -> a)
| ForkUI (Interaction (UIResult s)) a
| CallAPI (Interaction (APIResult s)) (APIResult s -> a)
| Get Store Key (Maybe String -> a)
| Set Store Key String a
| Fork (Flow s) (Control s -> a)
| DoAff (forall eff. AppFlow eff s) (s -> a)
| Await (Control s) (s -> a)
| Delay Milliseconds a
| OneOf (Array (Flow s)) (s -> a)
| HandleError (Flow (ErrorHandler s)) (s -> a)
| CheckPermissions (Array Permission) (PermissionStatus -> a)
| TakePermissions (Array Permission) (Array PermissionResponse -> a)
Presto
Framework
https://github.com/juspay/purescript-presto
The problem
Presto: flows
billPayFlow :: MobileNumber -> ScreenFlow
billPayFlow number = do
amount <- UI.askAmount
result <- Remote.payBill number amount
billPayStatus number amount result
billPayStatus :: MobileNumber -> Amount -> BillPayStatus -> ScreenFlow
billPayStatus number amount status = do
action <- runUI' (StatusScreen number amount status)
case action of
SuccessResult -> pure SuccessResult
StatusScreenAbort -> billPayFlow number -- recursion
BackT (MFlow)
main = runBackT $ do
lift $ log "Starting Run"
a <- backPoint (randomInt 1 10) -- point to return
lift $ log "A number"
unless (a >= 9) $ BackT (pure GoBack) -- return from here
b <- backPoint (randomInt 1 10) -- point to return
lift $ log "Another number"
unless (b >= 9) $ BackT (pure GoBack) -- return from here
lift $ logShow (a + b)
https://hackage.haskell.org/package/MFlow
BackT + flows: how it could possibly look
billPayFlow :: MobileNumber -> BackTScreenFlow
billPayFlow number = do
amount <- backPoint UI.askAmount -- point to return
result <- lift $ Remote.payBill number amount
billPayStatus number amount result
billPayStatus :: MobileNumber -> Amount -> BillPayStatus -> BackTScreenFlow
billPayStatus number amount status = do
action <- lift $ runUI' (StatusScreen number amount status)
case action of
SuccessResult -> pure SuccessResult
StatusScreenAbort -> BackT (pure GoBack) -- go back from here
BackT: not a monad, actually
-- https://gist.github.com/ninegua/97833cb4f82451f6c3db
-- Unfortunately, BackT is not a monad. It violates the associative
-- law, as the program shown below, test1 enters an infinite loop,
-- and test2 exits after printing 3 lines.
test1 = runBackT (a >> (b >> c))
test2 = runBackT ((a >> b) >> c)
a = lift (print "step 1") >> breturn ()
b = lift (print "step 2") >> return ()
c = lift (print "step 3") >> fail ""
Embedded DSLs design
1. Make it look good
3 steps of eDSL design
1. Make it look good
2. Make it compile
3 steps of eDSL design
1. Make it look good
2. Make it compile
3. Make it work
3 steps of eDSL design
detailsFlowTrans =
trans detailsFlow
</> [ onCalculate (trans calculateFlow) ]
/> [ onClose (trans submitFlow ) ]
detailsFlow :: Flow Int
detailsFlow = pure 10
calculateFlow :: Int -> Flow Unit
calculateFlow _ = pure unit
submitFlow :: Int -> Flow Unit
submitFlow _ = pure unit
Inventing eDSL syntax, try #1
homeFlowTrans =
trans homeFlow
<~> onClose (trans closeFlow)
operationsFlowTrans =
trans transactionFlow
<~> onDetails (trans detailsFlow )
<~> onSettings (trans settingsFlow)
~> onHome homeFlowTrans
Inventing eDSL syntax, try #2
operationsFlowTrans = do
t <- with transactionFlow
backableTrans t onDetails (trans detailsFlow )
backableTrans t onSettings (trans settingsFlow)
forwardOnlyTrans t onHome (trans homeFlow )
Maybe, monads?
operationsFlowTrans =
(forwardOnly
(backable
(backable
(trans transactionFlow)
(onDetails (trans detailsFlow))
)
(onSettings (trans settingsFlow))
)
(onHome (trans homeFlow))
)
Less is more. No syntax, please
detailsFlowGraph :: Graph Unit Unit
detailsFlowGraph = graph $
with detailsFlow
<~> on "calculate" (leaf1 calculateFlow)
~> on "enter" (leaf1 submitFlow )
Final version of eDSL
Transition Graph eDSL
node1 :: Graph IO () ()
node1 = graph $
with (print "node1" >> getInput)
<~> on "go 3" node3
~> on "go 2" node2
/> node1
node2 :: Graph IO () ()
node2 = graph $
with (print "node2" >> nop)
-/> node3
node3 :: Graph IO () ()
node3 = graph $
with (print "node3" >> getInput)
</> node1
~> on "exit" (leaf nop)
getInput :: IO (Event, ())
getInput = do
input <- getLine
pure (input, ())
nop :: IO (Event, ())
nop = pure ("", ())
main = runGraph' id (== "back") node1
Sample: Graph + IO
node1 :: Graph IO () Int
node1 = graph $
with (pure ("", 100500))
-/> node2
node2 :: Graph IO Int ()
node2 = graph $
with1 (n -> print n >> getInput)
<~> on "go 3" node3
/> node1
node3 :: Graph IO () ()
node3 = graph $
with (print "bye")
-/> (leaf nop)
getInput :: IO (Event, ())
getInput = do
input <- getLine
pure (input, ())
nop :: IO (Event, ())
nop = pure ("", ())
main = runGraph' id (== "back") node1
Typing
(~>) By event, forward-only
(<~>) By event, backable (uses a specified event to allow back transition)
(>~<) By event, go forward and return unconditionally
(/>) Default, forward-only
(</>) Default, backable
(-/>) Default, pass through node unconditionally (overrides other transitions)
Transitions
(~>) By event, forward-only
(<~>) By event, backable (uses a specified event to allow back transition)
(>~<) By event, go forward and return unconditionally
(/>) Default, forward-only
(</>) Default, backable
(-/>) Default, pass through node unconditionally (overrides other transitions)
Planned:
(?/>) Default, with a given condition
(!/>) Default, random with a given chance
(!~>) By event, random with a given chance
(?~>) By event, with a given condition
(!?~>) By event, with a given condition, random with a given chance
Transitions
Sample application
Game eDSL
data AdventureLF next where
GetUserInput :: (String -> next) -> AdventureLF next
PrintMessage :: String -> next -> AdventureLF next
GetObj :: FromJSON a => String -> (a -> next) -> AdventureLF next
type AdventureL = Free AdventureLF
Game eDSL
data AdventureLF next where
GetUserInput :: (String -> next) -> AdventureLF next
PrintMessage :: String -> next -> AdventureLF next
GetObj :: FromJSON a => String -> (a -> next) -> AdventureLF next
type AdventureL = Free AdventureLF
westOfHouse' :: (Bool, Bool) -> AdventureL ()
westOfHouse' (showDescr, showMailbox) = do
mailbox :: Mailbox <- getObject "mailbox"
printMessage "West of House"
when showDescr $ printMessage "This is an open field west of a white house."
when showMailbox $ printMessage $ describeObject mailbox
type AGGraph a b = Graph AdventureL a b
game :: AGGraph () ()
game = graph $
with (inputOnly (True, True))
-/> westOfHouse
westOfHouse :: AGGraph (Bool, Bool) ()
westOfHouse = graph $
with1 (x -> westOfHouse' x >> getInput)
~> on "open mailbox" (openMailbox (False, False))
/> leaf nop
openMailbox :: (Bool, Bool) -> AGGraph () ()
openMailbox houseView = graph $
with (evalAction MailboxType "open" "mailbox" >> inputOnly houseView)
-/> westOfHouse
Game transition graph
Game output
West of House
This is an open field west of a white house, with a boarded front door.
This is a small mailbox.
A rubber mat saying 'Welcome to Zork!' lies by the door.
> open mailbox
Opening mailbox revealed leaflet
> open mailbox
Mailbox already opened.
Implementation
newtype Graph lang i o
= Graph (Exists (GraphF lang i o))
data GraphF lang i o b
= GraphF1 (i -> lang (Event, b)) (Transitions lang b o ())
data Exists f where
Exists :: f a -> Exists f
Graph: existential data type
Graph lang i o
Exists (GraphF lang i o)
Graph lang i o
Exists (GraphF lang i o)
GraphF lang i o b
Graph lang i o
Exists (GraphF lang i o)
GraphF lang i o b
GraphF1 (i -> lang (Event, b))
(Transitions lang b o ())
Graph lang i o
Exists (GraphF lang i o)
GraphF lang i o b
GraphF1 (i -> lang (Event, b))
(Transitions lang b o ())
Transition (Graph lang b o)
Exists (GraphF lang b o)
Graph lang i o
Exists (GraphF lang i o)
GraphF lang i o b
GraphF1 (i -> lang (Event, b))
(Transitions lang b o ())
Transition (Graph lang b o)
Exists (GraphF lang b o)
GraphF lang b o b2
Graph lang i o
Exists (GraphF lang i o)
GraphF lang i o b
GraphF1 (i -> lang (Event, b))
(Transitions lang b o ())
Transition (Graph lang b o)
Exists (GraphF lang b o)
GraphF lang b o b2
GraphF1 (b -> lang (Event, b2))
(Transitions lang b2 o ())
module Data.Exists where
import Unsafe.Coerce (unsafeCoerce)
data Exists f where
Exists :: f a -> Exists f
mkExists :: forall f a. f a -> Exists f
mkExists = unsafeCoerce
runExists :: forall f r. (forall a. f a -> r) -> (Exists f -> r)
runExists = unsafeCoerce
Exists: hacky data type
with1
:: (Monad lang)
=> (i -> lang (Event, b))
-> Transitions lang b o ()
-> Graph lang i o
with1 langF1 table = Graph $ mkExists $ GraphF1 langF1 table
case matchTransition event prevGraph of
PassThrough (Graph graphEx) -> runExists (runTransition input) graphEx
Construction and deconstruction of existential type
with1
:: (Monad lang)
=> (i -> lang (Event, b))
-> Transitions lang b o ()
-> Graph lang i o
node0 = graph $
with something
<~> on "event 1" node1
~> on "event 2" node2
>~< on "event 3" node3
</> node4
/> node5
Transitions data type
type Transitions lang b o u = Free (TransitionF lang b o) u
data TransitionF lang b o next
= Transition Event (TransitionDef (Graph lang b o)) next
| PassThroughTransition (Graph lang b o) next
| PassDefaultForwardOnlyTransition (Graph lang b o) next
| PassDefaultBackableTransition (Graph lang b o) next
data TransitionDef graph
= Backable graph
| ForwardOnly graph
| AutoBack graph
| PassThrough graph
| PassDefaultForwardOnly graph
| PassDefaultBackable graph
| NoTransition
Transitions: Free monad data type
interpret
:: Event
-> TransitionF lang b o u
-> Interpreter (Graph lang b o) u
interpret currentEvent (PassThroughTransition g next) = do
transDef' <- get
case transDef' of
PassThrough _ -> pure next
_ -> put (PassThrough g) >> pure next
interpret currentEvent (PassDefaultBackableTransition g next) = do
transDef' <- get
case transDef' of
NoTransition -> put (PassDefaultBackable g) >> pure next
_ -> pure next
Transitions interpreter
(-/>)
(</>)
Running graph
Running graph
runTransition'
:: (Monad m, Monad lang)
=> Runtime lang m
-> ThisBackable
-> AutoReturn
-> i
-> GraphF lang i o b
-> m TransitionResult
runTransition' runtime backable autoReturn i3 g3 = do
let f3 = getLang i3 g3
transitionResult <- runTransition runtime autoReturn f3 g3
case transitionResult of
Done
-> pure Done
AutoFallbackRerun -> pure FallbackRerun
FallbackRerun
-> runTransition' runtime backable thisNotAutoReturn i3 g3
Fallback
->
if isBackable backable
then pure FallbackRerun
else pure Done -- throw "No fallback"
GoForward e2 i3 -> case matchTransition e2 g2 of
NoTransition
-> pure Done
PassThrough g3@(Graph g3Ex) ->
runExists (runTransition' runtime thisNotBackable thisNotAutoReturn i3) g3Ex
PassDefaultForwardOnly g3@(Graph g3Ex) ->
runExists (runTransition' runtime thisNotBackable thisNotAutoReturn i3) g3Ex
PassDefaultBackable g3@(Graph g3Ex) ->
runExists (runTransition' runtime thisBackable
thisNotAutoReturn i3) g3Ex
Backable
g3@(Graph g3Ex) ->
runExists (runTransition' runtime thisBackable
thisNotAutoReturn i3)
ForwardOnly g3@(Graph g3Ex) ->
runExists (runTransition' runtime thisNotBacka
AutoBack
g3@(Graph g3Ex) ->
runExists (runTransit
runLang
:: (Monad m, Monad lang)
=> Runtime lang m
-> lang (LangOutput a)
-> m (LangResult Event a)
runLang (Runtime runLang isBackEvent)
lang = do(e, i) <- runLang lang
if isBackEvent e
then pure GoBackward
else pure $ GoForward e i
data Runtime lang m = Runtime
{ runLang_ :: Runner
lang m
, isBackEvent_ :: Event ->
Bool
}
data LangResult a b =
GoForward a b | GoBackward
data TransitionResult
= Fallback
| AutoFallbackRerun
| FallbackRerun
| Done
Why you shouldn’t write such code
● Exists is hack Use GADTs
Why you shouldn’t write such code
● Exists is hack Use GADTs
● Overengineering Kmettism is out there
Why you shouldn’t write such code
● Exists is hack Use GADTs
● Overengineering Kmettism is out there
● Overengineering - 2 Free monad can be replaced by Map or list
Why you shouldn’t write such code
● Exists is hack Use GADTs
● Overengineering Kmettism is out there
● Overengineering - 2 Free monad can be replaced by Map
● Don't reinvent the wheel Use generic libraries for graphs
Why you shouldn’t write such code
● Exists is hack Use GADTs
● Overengineering Kmettism is out there
● Overengineering - 2 Free monad can be replaced by Map
● Don't reinvent the wheel Use generic libraries for graphs
● Don't reinvent the wheel - 2 You probably need FRP
Why you shouldn’t write such code
● Exists is hack Use GADTs
● Overengineering Kmettism is out there
● Overengineering - 2 Free monad can be replaced by Map
● Don't reinvent the wheel Use generic libraries for graphs
● Don't reinvent the wheel - 2 You probably need FRP
● Don't reinvent the wheel - 3 Is Graph In Out type just the Arrow In Out type?
Why you shouldn’t write such code
● Exists is hack Use GADTs
● Overengineering Kmettism is out there
● Overengineering - 2 Free monad can be replaced by Map
● Don't reinvent the wheel Use generic libraries for graphs
● Don't reinvent the wheel - 2 You probably need FRP
● Don't reinvent the wheel - 3 Is Graph In Out type just the Arrow In Out type?
● Don’t reinvent the wheel - 4 It’s just a State Machine, isn’t it?
Everyone knows that debugging is twice as hard as
writing a program in the first place. So if you're as
clever as you can be when you write it, how will you
ever debug it?
Brian Kernighan
Alexander Granin
graninas@gmail.com
Thanks for watching!

Transition graph using free monads and existentials

  • 1.
    Alexander Granin graninas@gmail.com eDSL fortransition graph using Free monads and existential types
  • 3.
    Structure ● The problem ●Embedded DSLs design ● Transition Graph eDSL ● Sample application ● Implementation ● Why you shouldn’t write such code
  • 4.
    data FlowMethodF as = RunUI (Interaction (UIResult s)) (UIResult s -> a) | ForkUI (Interaction (UIResult s)) a | CallAPI (Interaction (APIResult s)) (APIResult s -> a) | Get Store Key (Maybe String -> a) | Set Store Key String a | Fork (Flow s) (Control s -> a) | DoAff (forall eff. AppFlow eff s) (s -> a) | Await (Control s) (s -> a) | Delay Milliseconds a | OneOf (Array (Flow s)) (s -> a) | HandleError (Flow (ErrorHandler s)) (s -> a) | CheckPermissions (Array Permission) (PermissionStatus -> a) | TakePermissions (Array Permission) (Array PermissionResponse -> a) Presto Framework https://github.com/juspay/purescript-presto The problem
  • 5.
    Presto: flows billPayFlow ::MobileNumber -> ScreenFlow billPayFlow number = do amount <- UI.askAmount result <- Remote.payBill number amount billPayStatus number amount result billPayStatus :: MobileNumber -> Amount -> BillPayStatus -> ScreenFlow billPayStatus number amount status = do action <- runUI' (StatusScreen number amount status) case action of SuccessResult -> pure SuccessResult StatusScreenAbort -> billPayFlow number -- recursion
  • 6.
    BackT (MFlow) main =runBackT $ do lift $ log "Starting Run" a <- backPoint (randomInt 1 10) -- point to return lift $ log "A number" unless (a >= 9) $ BackT (pure GoBack) -- return from here b <- backPoint (randomInt 1 10) -- point to return lift $ log "Another number" unless (b >= 9) $ BackT (pure GoBack) -- return from here lift $ logShow (a + b) https://hackage.haskell.org/package/MFlow
  • 7.
    BackT + flows:how it could possibly look billPayFlow :: MobileNumber -> BackTScreenFlow billPayFlow number = do amount <- backPoint UI.askAmount -- point to return result <- lift $ Remote.payBill number amount billPayStatus number amount result billPayStatus :: MobileNumber -> Amount -> BillPayStatus -> BackTScreenFlow billPayStatus number amount status = do action <- lift $ runUI' (StatusScreen number amount status) case action of SuccessResult -> pure SuccessResult StatusScreenAbort -> BackT (pure GoBack) -- go back from here
  • 8.
    BackT: not amonad, actually -- https://gist.github.com/ninegua/97833cb4f82451f6c3db -- Unfortunately, BackT is not a monad. It violates the associative -- law, as the program shown below, test1 enters an infinite loop, -- and test2 exits after printing 3 lines. test1 = runBackT (a >> (b >> c)) test2 = runBackT ((a >> b) >> c) a = lift (print "step 1") >> breturn () b = lift (print "step 2") >> return () c = lift (print "step 3") >> fail ""
  • 9.
  • 10.
    1. Make itlook good 3 steps of eDSL design
  • 11.
    1. Make itlook good 2. Make it compile 3 steps of eDSL design
  • 12.
    1. Make itlook good 2. Make it compile 3. Make it work 3 steps of eDSL design
  • 13.
    detailsFlowTrans = trans detailsFlow </>[ onCalculate (trans calculateFlow) ] /> [ onClose (trans submitFlow ) ] detailsFlow :: Flow Int detailsFlow = pure 10 calculateFlow :: Int -> Flow Unit calculateFlow _ = pure unit submitFlow :: Int -> Flow Unit submitFlow _ = pure unit Inventing eDSL syntax, try #1
  • 14.
    homeFlowTrans = trans homeFlow <~>onClose (trans closeFlow) operationsFlowTrans = trans transactionFlow <~> onDetails (trans detailsFlow ) <~> onSettings (trans settingsFlow) ~> onHome homeFlowTrans Inventing eDSL syntax, try #2
  • 15.
    operationsFlowTrans = do t<- with transactionFlow backableTrans t onDetails (trans detailsFlow ) backableTrans t onSettings (trans settingsFlow) forwardOnlyTrans t onHome (trans homeFlow ) Maybe, monads?
  • 16.
    operationsFlowTrans = (forwardOnly (backable (backable (trans transactionFlow) (onDetails(trans detailsFlow)) ) (onSettings (trans settingsFlow)) ) (onHome (trans homeFlow)) ) Less is more. No syntax, please
  • 17.
    detailsFlowGraph :: GraphUnit Unit detailsFlowGraph = graph $ with detailsFlow <~> on "calculate" (leaf1 calculateFlow) ~> on "enter" (leaf1 submitFlow ) Final version of eDSL
  • 18.
  • 19.
    node1 :: GraphIO () () node1 = graph $ with (print "node1" >> getInput) <~> on "go 3" node3 ~> on "go 2" node2 /> node1 node2 :: Graph IO () () node2 = graph $ with (print "node2" >> nop) -/> node3 node3 :: Graph IO () () node3 = graph $ with (print "node3" >> getInput) </> node1 ~> on "exit" (leaf nop) getInput :: IO (Event, ()) getInput = do input <- getLine pure (input, ()) nop :: IO (Event, ()) nop = pure ("", ()) main = runGraph' id (== "back") node1 Sample: Graph + IO
  • 20.
    node1 :: GraphIO () Int node1 = graph $ with (pure ("", 100500)) -/> node2 node2 :: Graph IO Int () node2 = graph $ with1 (n -> print n >> getInput) <~> on "go 3" node3 /> node1 node3 :: Graph IO () () node3 = graph $ with (print "bye") -/> (leaf nop) getInput :: IO (Event, ()) getInput = do input <- getLine pure (input, ()) nop :: IO (Event, ()) nop = pure ("", ()) main = runGraph' id (== "back") node1 Typing
  • 21.
    (~>) By event,forward-only (<~>) By event, backable (uses a specified event to allow back transition) (>~<) By event, go forward and return unconditionally (/>) Default, forward-only (</>) Default, backable (-/>) Default, pass through node unconditionally (overrides other transitions) Transitions
  • 22.
    (~>) By event,forward-only (<~>) By event, backable (uses a specified event to allow back transition) (>~<) By event, go forward and return unconditionally (/>) Default, forward-only (</>) Default, backable (-/>) Default, pass through node unconditionally (overrides other transitions) Planned: (?/>) Default, with a given condition (!/>) Default, random with a given chance (!~>) By event, random with a given chance (?~>) By event, with a given condition (!?~>) By event, with a given condition, random with a given chance Transitions
  • 23.
  • 24.
    Game eDSL data AdventureLFnext where GetUserInput :: (String -> next) -> AdventureLF next PrintMessage :: String -> next -> AdventureLF next GetObj :: FromJSON a => String -> (a -> next) -> AdventureLF next type AdventureL = Free AdventureLF
  • 25.
    Game eDSL data AdventureLFnext where GetUserInput :: (String -> next) -> AdventureLF next PrintMessage :: String -> next -> AdventureLF next GetObj :: FromJSON a => String -> (a -> next) -> AdventureLF next type AdventureL = Free AdventureLF westOfHouse' :: (Bool, Bool) -> AdventureL () westOfHouse' (showDescr, showMailbox) = do mailbox :: Mailbox <- getObject "mailbox" printMessage "West of House" when showDescr $ printMessage "This is an open field west of a white house." when showMailbox $ printMessage $ describeObject mailbox
  • 26.
    type AGGraph ab = Graph AdventureL a b game :: AGGraph () () game = graph $ with (inputOnly (True, True)) -/> westOfHouse westOfHouse :: AGGraph (Bool, Bool) () westOfHouse = graph $ with1 (x -> westOfHouse' x >> getInput) ~> on "open mailbox" (openMailbox (False, False)) /> leaf nop openMailbox :: (Bool, Bool) -> AGGraph () () openMailbox houseView = graph $ with (evalAction MailboxType "open" "mailbox" >> inputOnly houseView) -/> westOfHouse Game transition graph
  • 27.
    Game output West ofHouse This is an open field west of a white house, with a boarded front door. This is a small mailbox. A rubber mat saying 'Welcome to Zork!' lies by the door. > open mailbox Opening mailbox revealed leaflet > open mailbox Mailbox already opened.
  • 28.
  • 29.
    newtype Graph langi o = Graph (Exists (GraphF lang i o)) data GraphF lang i o b = GraphF1 (i -> lang (Event, b)) (Transitions lang b o ()) data Exists f where Exists :: f a -> Exists f Graph: existential data type
  • 30.
    Graph lang io Exists (GraphF lang i o)
  • 31.
    Graph lang io Exists (GraphF lang i o) GraphF lang i o b
  • 32.
    Graph lang io Exists (GraphF lang i o) GraphF lang i o b GraphF1 (i -> lang (Event, b)) (Transitions lang b o ())
  • 33.
    Graph lang io Exists (GraphF lang i o) GraphF lang i o b GraphF1 (i -> lang (Event, b)) (Transitions lang b o ()) Transition (Graph lang b o) Exists (GraphF lang b o)
  • 34.
    Graph lang io Exists (GraphF lang i o) GraphF lang i o b GraphF1 (i -> lang (Event, b)) (Transitions lang b o ()) Transition (Graph lang b o) Exists (GraphF lang b o) GraphF lang b o b2
  • 35.
    Graph lang io Exists (GraphF lang i o) GraphF lang i o b GraphF1 (i -> lang (Event, b)) (Transitions lang b o ()) Transition (Graph lang b o) Exists (GraphF lang b o) GraphF lang b o b2 GraphF1 (b -> lang (Event, b2)) (Transitions lang b2 o ())
  • 36.
    module Data.Exists where importUnsafe.Coerce (unsafeCoerce) data Exists f where Exists :: f a -> Exists f mkExists :: forall f a. f a -> Exists f mkExists = unsafeCoerce runExists :: forall f r. (forall a. f a -> r) -> (Exists f -> r) runExists = unsafeCoerce Exists: hacky data type
  • 37.
    with1 :: (Monad lang) =>(i -> lang (Event, b)) -> Transitions lang b o () -> Graph lang i o with1 langF1 table = Graph $ mkExists $ GraphF1 langF1 table case matchTransition event prevGraph of PassThrough (Graph graphEx) -> runExists (runTransition input) graphEx Construction and deconstruction of existential type
  • 38.
    with1 :: (Monad lang) =>(i -> lang (Event, b)) -> Transitions lang b o () -> Graph lang i o node0 = graph $ with something <~> on "event 1" node1 ~> on "event 2" node2 >~< on "event 3" node3 </> node4 /> node5 Transitions data type
  • 39.
    type Transitions langb o u = Free (TransitionF lang b o) u data TransitionF lang b o next = Transition Event (TransitionDef (Graph lang b o)) next | PassThroughTransition (Graph lang b o) next | PassDefaultForwardOnlyTransition (Graph lang b o) next | PassDefaultBackableTransition (Graph lang b o) next data TransitionDef graph = Backable graph | ForwardOnly graph | AutoBack graph | PassThrough graph | PassDefaultForwardOnly graph | PassDefaultBackable graph | NoTransition Transitions: Free monad data type
  • 40.
    interpret :: Event -> TransitionFlang b o u -> Interpreter (Graph lang b o) u interpret currentEvent (PassThroughTransition g next) = do transDef' <- get case transDef' of PassThrough _ -> pure next _ -> put (PassThrough g) >> pure next interpret currentEvent (PassDefaultBackableTransition g next) = do transDef' <- get case transDef' of NoTransition -> put (PassDefaultBackable g) >> pure next _ -> pure next Transitions interpreter (-/>) (</>)
  • 41.
  • 42.
    Running graph runTransition' :: (Monadm, Monad lang) => Runtime lang m -> ThisBackable -> AutoReturn -> i -> GraphF lang i o b -> m TransitionResult runTransition' runtime backable autoReturn i3 g3 = do let f3 = getLang i3 g3 transitionResult <- runTransition runtime autoReturn f3 g3 case transitionResult of Done -> pure Done AutoFallbackRerun -> pure FallbackRerun FallbackRerun -> runTransition' runtime backable thisNotAutoReturn i3 g3 Fallback -> if isBackable backable then pure FallbackRerun else pure Done -- throw "No fallback" GoForward e2 i3 -> case matchTransition e2 g2 of NoTransition -> pure Done PassThrough g3@(Graph g3Ex) -> runExists (runTransition' runtime thisNotBackable thisNotAutoReturn i3) g3Ex PassDefaultForwardOnly g3@(Graph g3Ex) -> runExists (runTransition' runtime thisNotBackable thisNotAutoReturn i3) g3Ex PassDefaultBackable g3@(Graph g3Ex) -> runExists (runTransition' runtime thisBackable thisNotAutoReturn i3) g3Ex Backable g3@(Graph g3Ex) -> runExists (runTransition' runtime thisBackable thisNotAutoReturn i3) ForwardOnly g3@(Graph g3Ex) -> runExists (runTransition' runtime thisNotBacka AutoBack g3@(Graph g3Ex) -> runExists (runTransit runLang :: (Monad m, Monad lang) => Runtime lang m -> lang (LangOutput a) -> m (LangResult Event a) runLang (Runtime runLang isBackEvent) lang = do(e, i) <- runLang lang if isBackEvent e then pure GoBackward else pure $ GoForward e i data Runtime lang m = Runtime { runLang_ :: Runner lang m , isBackEvent_ :: Event -> Bool } data LangResult a b = GoForward a b | GoBackward data TransitionResult = Fallback | AutoFallbackRerun | FallbackRerun | Done
  • 43.
    Why you shouldn’twrite such code ● Exists is hack Use GADTs
  • 44.
    Why you shouldn’twrite such code ● Exists is hack Use GADTs ● Overengineering Kmettism is out there
  • 45.
    Why you shouldn’twrite such code ● Exists is hack Use GADTs ● Overengineering Kmettism is out there ● Overengineering - 2 Free monad can be replaced by Map or list
  • 46.
    Why you shouldn’twrite such code ● Exists is hack Use GADTs ● Overengineering Kmettism is out there ● Overengineering - 2 Free monad can be replaced by Map ● Don't reinvent the wheel Use generic libraries for graphs
  • 47.
    Why you shouldn’twrite such code ● Exists is hack Use GADTs ● Overengineering Kmettism is out there ● Overengineering - 2 Free monad can be replaced by Map ● Don't reinvent the wheel Use generic libraries for graphs ● Don't reinvent the wheel - 2 You probably need FRP
  • 48.
    Why you shouldn’twrite such code ● Exists is hack Use GADTs ● Overengineering Kmettism is out there ● Overengineering - 2 Free monad can be replaced by Map ● Don't reinvent the wheel Use generic libraries for graphs ● Don't reinvent the wheel - 2 You probably need FRP ● Don't reinvent the wheel - 3 Is Graph In Out type just the Arrow In Out type?
  • 49.
    Why you shouldn’twrite such code ● Exists is hack Use GADTs ● Overengineering Kmettism is out there ● Overengineering - 2 Free monad can be replaced by Map ● Don't reinvent the wheel Use generic libraries for graphs ● Don't reinvent the wheel - 2 You probably need FRP ● Don't reinvent the wheel - 3 Is Graph In Out type just the Arrow In Out type? ● Don’t reinvent the wheel - 4 It’s just a State Machine, isn’t it?
  • 50.
    Everyone knows thatdebugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it? Brian Kernighan
  • 51.