Created
July 20, 2022 11:53
-
-
Save SystemFw/c004f3fc886e2d86fec01acb3a70c9d5 to your computer and use it in GitHub Desktop.
Can Stream I/O represent transformational programs?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment