From c607dd52773edd51134db573eb170dd402864ad1 Mon Sep 17 00:00:00 2001 From: Alexander Nutz Date: Fri, 12 Sep 2025 14:22:57 +0200 Subject: [PATCH] v0.0.11: proper pure IO --- spec.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/spec.md b/spec.md index 93e0229..cdf6d78 100644 --- a/spec.md +++ b/spec.md @@ -376,22 +376,67 @@ myHeader with header:text: "new meta:header:text value" ## Pure IO +the `IO[t]` type contains IO "plans" which will be executed by the runtime, if they are returned by the main function. +``` +def main() -> IO[Unit] { + print("hey") # warning: result (of type IO[Unit]) was ignored. expression can be removed + print("hello") +} +# this will only print "hello" +``` +To make the above example print both "hey" and "hello", we need to chain the two IO types: +``` +def main() -> IO[Unit] { + await _ = print("hey") + await print("hello") +} + +# or just remove the _ +def main() -> IO[Unit] { + await print("hey") + await print("hello") +} + +# if you don't put in the second await: +def main() -> IO[Unit] { + await print("hey") + print("hello") + # error: expected IO[Unit], got IO[IO[Unit]]; did you forget an `await`? +} +``` + +await is kinda weird. here is the syntax: +``` +# ( await x: a = ( expr1::IO[a] ) await (expr2::IO[b]) ) :: IO[b] +expr |= 'await', (identifier, '='), expr, 'await', expr + +# ( await x: a = ( expr1::IO[a] ) ( expr2::b ) ) :: IO[b] +expr |= 'await', (identifier, '='), expr, expr + +# ( await (expr1::a) ) :: a +expr |= 'await', expr +``` + + +## Pure IO implementation Something like this is done in the stdlib: ``` extensible union IO.Plan[r] type IO[t] = 'Just {value: t} - | 'More template r, f: {plan: IO.Plan[r], then: r -> IO[f], finally: f -> t} + | 'Map template r: {of: IO[r], map: r -> IO[t]} + | 'More template r: {plan: IO.Plan[r], then: r -> IO[t]} def [a,b] `await a (a->b)`(io: IO[a], then: a -> b) -> IO[b] { - match io { - 'Just {value} -> 'Just {value: then(value)}, - 'More &a {finally,...} -> a with finally: r -> then(finally(r)) - } + 'Map {of: io, map: r -> 'Just {value: then(r)}} +} + +def [a,b] `await a (a->await b)`(io: IO[a], then: a -> IO[b]) -> IO[b] { + 'Map {of: io, map: r -> then(r)} } # in stdio: extend IO.Plan[Uint8] with 'stdio.ReadByte {stream: Int32} -def stdio.getchar : IO[Uint8] = 'More {plan: 'stdio.ReadByte {stream: 0}, then: by -> 'Just by[0], finally: x->x} +def stdio.getchar : IO[Uint8] = 'More {plan: 'stdio.ReadByte {stream: 0}, then: by -> 'Just by[0]} def main() -> IO[Unit] ``` @@ -401,10 +446,11 @@ the runtime does something like this: def [a] RUNTIME_EVAL(io: IO[a]) -> a { match io { 'Just {value} -> value - 'More {plan, then, finally} -> finally(RUNTIME_EVAL(then(match plan { + 'Map {of, map} -> RUNTIME_EVAL(map(RUNTIME_EVAL(of))) + 'More {plan, then, finally} -> RUNTIME_EVAL(then(match plan { 'ReadStream {stream} -> 'ReadStreamIOResult {data: impure perform the io here lol} _ -> impure error here "this runtime doesn't support this kind of IO" or sth - }))) + })) } }