Skip to content

Instantly share code, notes, and snippets.

@SystemFw
Created July 20, 2022 11:53
Show Gist options
  • Save SystemFw/c004f3fc886e2d86fec01acb3a70c9d5 to your computer and use it in GitHub Desktop.
Save SystemFw/c004f3fc886e2d86fec01acb3a70c9d5 to your computer and use it in GitHub Desktop.
Can Stream I/O represent transformational programs?
-- 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