-- The assumption here is that stream based I/O is a better fit for -- distributed systems than monadic I/O, because there is a greater -- separation between logic, which can be idempotent and distributed -- via something like differential dataflow, and actions, which can -- be represented like ports and actuators -- We already know that stream based I/O is a good fit for reactive programs -- but what about more "imperative" ones that fit monads well? I took an imperative -- example and tried to express it via stream I/O -- I'm using the only modern language I know to expose both models, i.e. Elm, where -- the Elm architecture is stream I/O (essentially dialogues with Cmd and Msg), -- whereas Task is monadic I/O. -- In that context, my question is essentially: can we eliminate Task? -- Can run it on https://elm-lang.org/try: import Html exposing (..) import Browser import Html.Events exposing (..) import Html.Attributes exposing (..) import Json.Decode as Json main = Browser.sandbox (connect secondProgram) type Query id = In id String | Out id | Start type Command id = Write id String | Read id | End myProgram: Query Int -> Command Int myProgram query = case query of Start -> Read 0 Out id -> Read (id + 1) In id text -> Write (id + 1) text secondProgram: Query String -> Command String secondProgram query = case query of Start -> Write "greeting" "What's your name?" Out "greeting" -> Read "greeting" In "greeting" name -> if name /= "fabio" then Write "retry" "Please retry, you're not fabio" else Write "end" (String.concat ["Hello ", name, "!"]) Out "retry" -> secondProgram Start Out "end" -> End _ -> Debug.todo "That's how the model works" -- def secondProgram: IO[Unit] = -- IO.println("What's your name") >> -- IO.readLine.flatMap { name => -- if (name != "fabio") -- IO.println("Please retry, you're not fabio") >> secondProgram -- else IO.println(s"Hello $fabio!") -- } -- The comparison is not too bad, considering that the first program -- can be mad fully reactive just by switching the Port, and that we -- picked a problem well suited to the second program --- --- Code below should be in library code, it's the Console Port --- connect program = { init = run Start program (Console Nothing "" []) , update = update program , view = view } type alias Console id = { inputId: Maybe id , stdIn: String , stdOut: List String } run: Query id -> (Query id -> Command id) -> Console id -> Console id run query program console = case program query of Write id line -> let newConsole = { console | stdOut = console.stdOut ++ [line] } newQuery = Out id in run newQuery program newConsole Read id -> { console | inputId = Just id, stdIn = ""} End -> { console | inputId = Nothing, stdIn = "" } type Msg = Enter | Input String update: (Query id -> Command id) -> Msg -> Console id -> Console id update program msg console = case msg of Input text -> { console | stdIn = text} Enter -> console.inputId |> Maybe.map (\id -> run (In id console.stdIn) program console) |> Maybe.withDefault console view console = div [] [ div [] (List.map (\line -> div [] [text line]) console.stdOut) , input [onInput Input, onEnter Enter, value console.stdIn] [] ] onEnter: msg -> Attribute msg onEnter msg = let enterDecoder = keyCode |> Json.andThen (\key -> if key == 13 then Json.succeed msg else Json.fail "" ) in on "keydown" enterDecoder