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.

Transition graph using free monads and existentials

65 views

Published on

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

Published in: Technology
  • 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!

×