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 "'")) ((#\") (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)) "</Channel>\n")) and (cout "Content-type: text/html\n\n" "[snipped]" "<P><SELECT NAME=type_id><OPTION VALUE=\"\">Select Category" (lambda () (query->OPTIONS check-them: (cgi#type_id :as-list) "SELECT type_id, descr FROM TypesOfThings;" )) "</SELECT>\n<P><BR>\n" "<P>You need to enter a description for the created channel\n" "[more snipped]") In the following example, a lot of promises are being made and passed from one function to another... ; Make a Channel activation record, a multi-part message, ; given part-promises, thunks that output specific parts ; of the message (define (pack-car part-promise . other-promises) (let ((boundary "boundary-MCCM yradnuob")) (cout "Content-Type: text/x-car\n\n" "Content-Type: multipart/mixed; boundary=\"" boundary "\"\n") (for-each (lambda (part-writer) (cout nl "--" boundary nl part-writer)) (cons part-promise other-promises)) (cout nl "--" boundary "--" nl))) ..... (pack-car (make-send-file-promise "sub.html" write-html-bookmark) (make-send-file-promise "things.mbl" (lambda () (cout (list sub-id (list "products" (cons "DiscreteThings" cids)))))) (lambda () (cout "Content-type: text/x-mc-activate; sub-id=\"" sub-id "\"\n\n"))) Needless to say that write-html-bookmark is a promise too. I have tried to say that like OO, monadic programming is more a matter of style rather than that of a language. Thank you, Oleg http://pobox.com/~oleg/ftp/Scheme/ -----== Posted via Deja News, The Leader in Internet Discussion ==----- http://www.dejanews.com/ Now offering spam-free web-based newsreading