Interacting with the outside world
So far the todos only exist in the code. In this chapter the program
prints them, then reads them from a file. Write it in
app/hello/main.hf, in place of the greeting.
Printing the todos
module app.hello.main
import coimport hello.todoimport std.io
printTodos : {e : HFunctor} -> {{io : stdio <: e}} -> List Todo -> Co e UnitprintTodos [] = pure MkUnitprintTodos (t :: ts) = do print (show t) printTodos ts
main : Co stdio Unitmain = printTodos [ todo "write" , finish (todo "read") ]$ heff run app/hello/main.hf[ ] write[x] readRead printTodos’s type from the left:
{e : HFunctor}: an implicit argument, the world the computation runs in; it can be any world.{{io : stdio <: e}}: an instance argument,io, showing that stdio is part of the worlde.Co e Unit: a computation in the worlde, which gives backUnitwhen it is done.
do runs its lines one after another. main runs in the world stdio
alone, and printTodos asks for the evidence that stdio is part of
stdio: heff finds it.
Reading a file
Add import exception, import std.filesystem and import std.path to
the imports, then:
loadTodos : {e : HFunctor} -> {{disk : filesystem <: e}} -> {{ex : exception FileError <: e}} -> Path -> Co e (List Todo)loadTodos path = do text <- readFile path pure (List.map todo (lines text))loadTodos reads the file at path, a Path, and makes each of its
lines an open todo. text <- readFile path runs readFile and names its
result text, the file’s text. Where the file cannot be read, readFile
throws why, a FileError — the system’s errno, what it says of it, and
the path it says it of: so loadTodos needs exception FileError as
well as filesystem.
As you can see, printTodos needs stdio and loadTodos needs
filesystem: a computation must claim the parts of the world it
interacts with.
Put it all together
program : {e : HFunctor} -> {{io : stdio <: e}} -> {{disk : filesystem <: e}} -> {{ex : exception FileError <: e}} -> Co e Unitprogram = do ts <- loadTodos "todos.txt" printTodos ts
main : Co (stdio <+> filesystem) Unitmain = do r <- attempt FileError program case r of Left err -> print (show err) Right _ -> pure MkUnitloadTodos "todos.txt" gives it a string where a Path is expected:
std.path takes a string as a path, read as the system the program runs
on writes one.
program needs all three. main’s world, stdio <+> filesystem, has
stdio and the files; attempt FileError runs program in that world,
as main’s type says it, with exception FileError added on its left,
and gives back Left what was thrown, or Right what program gave
(see handlers and
attempt). Where nothing heard a throw,
main prints it.
Here heff finds where each effect program and print ask for is in
the world, attempt’s and main’s, and shows it so (see
giving the evidence).
$ printf 'write the tutorial\nread the reference' > todos.txt$ heff run app/hello/main.hf[ ] write the tutorial[ ] read the reference$ rm todos.txt$ heff run app/hello/main.hftodos.txt: ENOENT: No such file or directoryOutput, bytes and time
What print and write send to a file or a pipe is kept and written a
buffer at a time, and all of it by the time the program ends; to a
terminal, a line at a time. A program that runs for long, such as a
server writing a log, calls flush to have
what it printed written now. The standard error keeps nothing.
readFile gives a file’s text; readBytes
gives its Bytes, as they are. A string is written as its bytes are, so
write (Bytes.asString b) writes the bytes b to the output unchanged,
and so does std.file’s withStdout with writeBytes.
show of bytes is for text: it reads them as UTF-8, and what is
not UTF-8 becomes U+FFFD, which is not what a PNG wants.
std.time has sleep, for a Duration
(std.duration), and two clocks: now, the time of day, an Instant
(std.instant), and monotonic, a Duration that only goes forward and
measures how long something took.