Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
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 sho...
data FlowMethodF a s
= RunUI (Interaction (UIResult s)) (UIResult s -> a)
| ForkUI (Interaction (UIResult s)) a
| CallAPI ...
Presto: flows
billPayFlow :: MobileNumber -> ScreenFlow
billPayFlow number = do
amount <- UI.askAmount
result <- Remote.pa...
BackT (MFlow)
main = runBackT $ do
lift $ log "Starting Run"
a <- backPoint (randomInt 1 10) -- point to return
lift $ log...
BackT + flows: how it could possibly look
billPayFlow :: MobileNumber -> BackTScreenFlow
billPayFlow number = do
amount <-...
BackT: not a monad, actually
-- https://gist.github.com/ninegua/97833cb4f82451f6c3db
-- Unfortunately, BackT is not a mona...
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 ) ]
detailsF...
homeFlowTrans =
trans homeFlow
<~> onClose (trans closeFlow)
operationsFlowTrans =
trans transactionFlow
<~> onDetails (tr...
operationsFlowTrans = do
t <- with transactionFlow
backableTrans t onDetails (trans detailsFlow )
backableTrans t onSettin...
operationsFlowTrans =
(forwardOnly
(backable
(backable
(trans transactionFlow)
(onDetails (trans detailsFlow))
)
(onSettin...
detailsFlowGraph :: Graph Unit Unit
detailsFlowGraph = graph $
with detailsFlow
<~> on "calculate" (leaf1 calculateFlow)
~...
Transition Graph eDSL
node1 :: Graph IO () ()
node1 = graph $
with (print "node1" >> getInput)
<~> on "go 3" node3
~> on "go 2" node2
/> node1
n...
node1 :: Graph IO () Int
node1 = graph $
with (pure ("", 100500))
-/> node2
node2 :: Graph IO Int ()
node2 = graph $
with1...
(~>) By event, forward-only
(<~>) By event, backable (uses a specified event to allow back transition)
(>~<) By event, go ...
(~>) By event, forward-only
(<~>) By event, backable (uses a specified event to allow back transition)
(>~<) By event, go ...
Sample application
Game eDSL
data AdventureLF next where
GetUserInput :: (String -> next) -> AdventureLF next
PrintMessage :: String -> next ...
Game eDSL
data AdventureLF next where
GetUserInput :: (String -> next) -> AdventureLF next
PrintMessage :: String -> next ...
type AGGraph a b = Graph AdventureL a b
game :: AGGraph () ()
game = graph $
with (inputOnly (True, True))
-/> westOfHouse...
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....
Implementation
newtype Graph lang i o
= Graph (Exists (GraphF lang i o))
data GraphF lang i o b
= GraphF1 (i -> lang (Event, b)) (Transit...
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 ())
Transit...
Graph lang i o
Exists (GraphF lang i o)
GraphF lang i o b
GraphF1 (i -> lang (Event, b))
(Transitions lang b o ())
Transit...
Graph lang i o
Exists (GraphF lang i o)
GraphF lang i o b
GraphF1 (i -> lang (Event, b))
(Transitions lang b o ())
Transit...
module Data.Exists where
import Unsafe.Coerce (unsafeCoerce)
data Exists f where
Exists :: f a -> Exists f
mkExists :: for...
with1
:: (Monad lang)
=> (i -> lang (Event, b))
-> Transitions lang b o ()
-> Graph lang i o
with1 langF1 table = Graph $ ...
with1
:: (Monad lang)
=> (i -> lang (Event, b))
-> Transitions lang b o ()
-> Graph lang i o
node0 = graph $
with somethin...
type Transitions lang b o u = Free (TransitionF lang b o) u
data TransitionF lang b o next
= Transition Event (TransitionD...
interpret
:: Event
-> TransitionF lang b o u
-> Interpreter (Graph lang b o) u
interpret currentEvent (PassThroughTransiti...
Running graph
Running graph
runTransition'
:: (Monad m, Monad lang)
=> Runtime lang m
-> ThisBackable
-> AutoReturn
-> i
-> GraphF lang ...
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...
Why you shouldn’t write such code
● Exists is hack Use GADTs
● Overengineering Kmettism is out there
● Overengineering - 2...
Why you shouldn’t write such code
● Exists is hack Use GADTs
● Overengineering Kmettism is out there
● Overengineering - 2...
Why you shouldn’t write such code
● Exists is hack Use GADTs
● Overengineering Kmettism is out there
● Overengineering - 2...
Why you shouldn’t write such code
● Exists is hack Use GADTs
● Overengineering Kmettism is out there
● Overengineering - 2...
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 ...
Alexander Granin
graninas@gmail.com
Thanks for watching!
Transition graph using free monads and existentials
Upcoming SlideShare
Loading in …5
×

Transition graph using free monads and existentials

Transition graph, adventure game, free monads and existential types.

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all
  • Be the first to comment

  • Be the first to like this

Transition graph using free monads and existentials

  1. 1. Alexander Granin graninas@gmail.com eDSL for transition graph using Free monads and existential types
  2. 2. Structure ● The problem ● Embedded DSLs design ● Transition Graph eDSL ● Sample application ● Implementation ● Why you shouldn’t write such code
  3. 3. 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
  4. 4. 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
  5. 5. 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
  6. 6. 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
  7. 7. 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 ""
  8. 8. Embedded DSLs design
  9. 9. 1. Make it look good 3 steps of eDSL design
  10. 10. 1. Make it look good 2. Make it compile 3 steps of eDSL design
  11. 11. 1. Make it look good 2. Make it compile 3. Make it work 3 steps of eDSL design
  12. 12. 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
  13. 13. homeFlowTrans = trans homeFlow <~> onClose (trans closeFlow) operationsFlowTrans = trans transactionFlow <~> onDetails (trans detailsFlow ) <~> onSettings (trans settingsFlow) ~> onHome homeFlowTrans Inventing eDSL syntax, try #2
  14. 14. operationsFlowTrans = do t <- with transactionFlow backableTrans t onDetails (trans detailsFlow ) backableTrans t onSettings (trans settingsFlow) forwardOnlyTrans t onHome (trans homeFlow ) Maybe, monads?
  15. 15. operationsFlowTrans = (forwardOnly (backable (backable (trans transactionFlow) (onDetails (trans detailsFlow)) ) (onSettings (trans settingsFlow)) ) (onHome (trans homeFlow)) ) Less is more. No syntax, please
  16. 16. detailsFlowGraph :: Graph Unit Unit detailsFlowGraph = graph $ with detailsFlow <~> on "calculate" (leaf1 calculateFlow) ~> on "enter" (leaf1 submitFlow ) Final version of eDSL
  17. 17. Transition Graph eDSL
  18. 18. 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
  19. 19. 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
  20. 20. (~>) 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
  21. 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) 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
  22. 22. Sample application
  23. 23. 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
  24. 24. 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
  25. 25. 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
  26. 26. 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.
  27. 27. Implementation
  28. 28. 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
  29. 29. Graph lang i o Exists (GraphF lang i o)
  30. 30. Graph lang i o Exists (GraphF lang i o) GraphF lang i o b
  31. 31. Graph lang i o Exists (GraphF lang i o) GraphF lang i o b GraphF1 (i -> lang (Event, b)) (Transitions lang b o ())
  32. 32. 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)
  33. 33. 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
  34. 34. 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 ())
  35. 35. 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
  36. 36. 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
  37. 37. 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
  38. 38. 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
  39. 39. 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 (-/>) (</>)
  40. 40. Running graph
  41. 41. 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
  42. 42. Why you shouldn’t write such code ● Exists is hack Use GADTs
  43. 43. Why you shouldn’t write such code ● Exists is hack Use GADTs ● Overengineering Kmettism is out there
  44. 44. 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
  45. 45. 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
  46. 46. 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
  47. 47. 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?
  48. 48. 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?
  49. 49. 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
  50. 50. Alexander Granin graninas@gmail.com Thanks for watching!

×