Report

Share

Follow

•0 likes•473 views

•0 likes•473 views

Report

Share

Download to read offline

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

Follow

- 1. Alexander Granin graninas@gmail.com eDSL for transition 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 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
- 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 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 ""
- 10. 1. Make it look good 3 steps of eDSL design
- 11. 1. Make it look good 2. Make it compile 3 steps of eDSL design
- 12. 1. Make it look 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 :: Graph Unit Unit detailsFlowGraph = graph $ with detailsFlow <~> on "calculate" (leaf1 calculateFlow) ~> on "enter" (leaf1 submitFlow ) Final version of eDSL
- 19. 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
- 20. 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
- 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
- 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
- 25. 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
- 26. 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
- 27. 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.
- 28. Implementation
- 29. 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
- 30. Graph lang i o Exists (GraphF lang i o)
- 31. Graph lang i o Exists (GraphF lang i o) GraphF lang i o b
- 32. Graph lang i o Exists (GraphF lang i o) GraphF lang i o b GraphF1 (i -> lang (Event, b)) (Transitions lang b o ())
- 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)
- 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
- 35. 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 ())
- 36. 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
- 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 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
- 40. 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 (-/>) (</>)
- 41. Running graph
- 42. 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
- 43. Why you shouldn’t write such code ● Exists is hack Use GADTs
- 44. Why you shouldn’t write such code ● Exists is hack Use GADTs ● Overengineering Kmettism is out there
- 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 or list
- 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
- 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
- 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?
- 49. 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?
- 50. 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
- 51. Alexander Granin graninas@gmail.com Thanks for watching!