Path: nntp.gmd.de!news.ruhr-uni-bochum.de!informatik.tu-muenchen.de!fu-berlin.de!nntprelay.mathworks.com!news-peer-east.sprintlink.net!news-peer.sprintlink.net!news.sprintlink.net!Sprint!cpk-news-hub1.bbnplanet.com!news.bbnplanet.com!nntp2.dejanews.com!nnrp1.dejanews.com!not-for-mail
From: oleg@pobox.com
Newsgroups: comp.lang.scheme,comp.lang.lisp,comp.lang.functional
Subject: Monadic scheme of I/O
Date: Sun, 29 Mar 1998 15:45:07 -0600
Organization: Deja News - The Leader in Internet Discussion
Lines: 132
Message-ID: <6fmf85$rj3$1@nnrp1.dejanews.com>
Reply-To: oleg@pobox.com
Summary: Monadic programming in Scheme: it's all a matter of style
Keywords: monad, i/o, Scheme, CGI, promise, interaction, referential transparency
X-Article-Creation-Date: Sun Mar 29 21:45:07 1998 GMT
X-Http-User-Agent: Mozilla/4.04 (Macintosh; I; PPC, Nav)
Xcanpos: shelf.01/199804110201!0023180241
Xref: nntp.gmd.de comp.lang.scheme:19296 comp.lang.lisp:24938 comp.lang.functional:9433
Haskell shows best and brightest the glamor of monads in performing
i/o, calling C functions and doing other _interactions_. Other
languages however are not barred from partaking in these riches. With
an appropriate _style_, one can closely emulate monadic i/o, as this
article tries to show on an example of Scheme. Scheme thus allows both
unbridled side-effecting interaction, and a "pure", monadic IO where
all worldly effects are performed only by a distinguished actor.
Let me borrow an example from a wonderful paper by Philip Wadler "How
to Declare an Imperative" (ACM Computing Surveys, 1997, v. 29, N3,
pp. 240-263). Let's start with two intentionally wrong examples:
(let ((x (display "ha")))
(cout "We said " x "-" x "\n"))
As Wadler noted, the laugh is on us. If you run this code, you'll see
what he meant: only a single "ha" would be printed, as a side-effect
at the time variable x is bound. This obviously wasn't the _right_
time. The original "wrong" example in Wadler's paper was written in
SML, btw, and was to illustrate referential opaqueness of i/o in
SML. Scheme can be just as "transparent".
(let ((x (lambda () (display "ha"))))
(cout "We said " (x) "-" (x) "\n"))
Now, two "ha" are printed, but again at the wrong moment: "hahaWe said
#-#". The action - printing - occurs when the arguments
of cout are evaluated, rather than at the time their values are
actually "used". Fortunately, it's easy to fix:
(let ((x (lambda () (display "ha"))))
(cout "We said " x "-" x "\n"))
This expression, when evaluated, prints exactly what is expected:
"We said ha-ha"
As Wadler explained, x is an abstraction of an interaction (expressed
in Scheme, this makes a neat pun). Therefore, x is not an action
itself but merely a "promise" of it. The action is exacted only when
the monad is "applied". Then the world shall change. Until that
moment however promises can be used as "pure" values: passed around,
returned from functions, bound to symbols, etc. It is when 'cout'
calls on the collected promises that the "delayed" actions are
performed, in the right sequence.
Here's the definition of cout, the distinguished actor and "debt
collector":
(define (cout . args)
(for-each (lambda (x)
(if (procedure? x) (x) (display x)))
args))
This monadic i/o is not merely a trick: I use it rather frequently,
for example, in writing CGI scripts in Scheme. The output from a CGI
script is supposed to follow special conventions (that is, to begin
with HTTP headers). Monads make it easy to decouple "output
production" from "output arrangement". With due apologies, here are a
few snippets from some of CGI code of mine:
; Given a string 'str' make a promise to write this string in an XML
; nice way, that is, paying attention to quote #\< and other characters
; that needed to be "escaped"
(define (promise-nice-xml-string str)
(lambda ()
(do ((i 0 (++ i))) ((>= i (string-length str)))
(let ((c (string-ref str i)))
(case c
((#\) (display ">"))
((#\&) (display "&"))
((#\') (display "'"))
((#\") (display """))
(else (write-char c)))))))
; export one Channel "type"
; given one row of the TypesOfThings
(define (export-a-type type_id mime_type descr)
(cout "\n" (promise-nice-xml-string descr)
"\n\n\n"
(lambda () (export-category-instances type_id mime_type))
"\n"))
and
(cout "Content-type: text/html\n\n" "[snipped]"
"