A bit more about IO
Funky has an unusual approach to side-effects. There's no notion of them in the language itself. What looked like side-effecting functions in the previous sections (println
, quit
, etc.) were, in fact, data structure constructors. The IO
type is not magic at all. It's just a plain data structure defined in the Funky language itself. Here's its definition (you can also find it in the standard library in the cmd
folder):
union IO = quit | putc Char IO | getc (Char -> IO)
We haven't covered union
yet but think algebraic data types (e.g. data
in Haskell). This IO
data structure serves as a description of what the program should do. However, in contrast with the control flow of traditional imperative languages, the IO
data structure can be arbitrarily manipulated - e.g. it's no problem to implement input/output encryption just by implementing a single IO -> IO
function.
Note. The
IO
data structure is very limited. All it can express is printing and scanning characters. The thing we haven't talked about yet is thatfunkycmd
is just one of the many possible side-effect interpreters. Specifically,funkycmd
interprets (executes) the aboveIO
data structure. There's another interpreter calledfunkygame
that interprets an entirely different data structure which describes sprite-based games. You can actually make your own side-effect interpreter fairly easily. The specifics of that are explained in another part.
The IO
looks like a linked list with two kinds of nodes (not counting quit
). The funkycmd
interpreter loads the main
function, walks the IO
nodes, and does as they say.
Let's talk about the nodes individually.
-
quit
. We've seen this one before - it marks the end of the program. -
putc
. This node has two arguments: aChar
and anIO
. TheChar
is the character to be printed. TheIO
tells what should be done next. -
getc
. Now this one looks strange. It has one argument: a function. The function takes aChar
and returns anIO
. Here's howfunkycmd
deals withgetc
: Upon encountering thegetc
node,funkycmd
scans a character from the standard input. Then it takes the function undergetc
and applies it to the scanned character, passes it inside. The function gives back anIO
. ThisIO
tells what should be done next.
Now let's see them in action!
How about a very exciting program that reads a character and prints it back?
func main : IO =
getc (\c putc c quit)
Run it:
$ funkycmd getcputc.fn
x
x
It worked!
Now, we can structure the code better using the same tricks we used with if
and let
. How does this look?
func main : IO =
getc \c
putc c;
quit
Well, that's nice! The first line scans a character, the second line prints it back, the third line quits the program.
Wait, what would happen if we made it recursive? What if we replaced quit
with main
?
func main : IO =
getc \c
putc c;
main
By the looks of it, this program should print back all of its input - it's the cat
program.
Note. But it never quits! Or does it? Currently,
funkycmd
is made to silently quit when encountering the EOF, so the program quits correctly. Of course, this behavior is not guaranteed to be kept.
$ funkycmd cat.fn
hello, cat!
hello, cat!
do you cat?
do you cat?
you do cat!
you do cat!
^D
The ^D
sequence at the end is the EOF.
Scanning more than one character
The standard library gives us many goods. There's print
and println
that work by expanding to multiple putc
nodes. Are there some analogous high-level functions for scanning? Sure there are! They're called, how unexpected, scan
and scanln
. What are their types:
$ funkycmd -types
> scan
(String -> IO) -> IO
> scanln
(String -> IO) -> IO
Oh nice, so their type is the same as the type of getc
, except the Char
is changed to String
.
The scanln
function scans a whole line and gives it back to you (not including the newline character). The scan
function is a little more clever: it reads the next "word" from the input - it skips all the whitespace and captures a continuous string of non-whitespace characters.
We'll get to use scan
more in the next section. Here's some scanln
for the taste:
func main : IO =
print "What's your name? ";
scanln \name
println ("Hello, " ++ name ++ "!");
quit
And there we go:
$ funkycmd greeting.fn
What's your name? Michal Štrba
Hello, Michal Štrba!