[Haskell] line-based interactive program
Wolfgang Jeltsch
wolfgang at jeltsch.net
Tue Jul 12 06:02:30 EDT 2005
Am Montag, 11. Juli 2005 15:51 schrieben Sie:
> [...]
> I am always interested in functional I/O solutions that adopt the
> "world-as-value" paradigm (or the more verbose "explicit multiple
> environment passing" paradigm) that has been exploited in Clean's file I/O
> system and GUI library. Your idea sounds interesting, but your explanation
> above of runFileIO and finishFileIO raises a few questions:
>
> (1) Suppose you have a file with content "abcde" at path p. What does the
> following program fragment yield?
> do
> (r1,h1) <- runFileIO p readEntireFile
> (r2,h2) <- runFileIO p readEntireFile
> return hd r1 : hd r2
You mean return hd r1 : hd r2 : [], don't you?
> where readEntireFile reads the entire file and returns it as a string. I
> can imagine several results: [a,a], [a,b], [a,_|_], [_|_,_|_], _|_.
I decided to distinguish between read-only I/O and write-permitted I/O. If
readEntireFile is declared as read-only I/O (which would be sensible) then
the above code would return ['a','a'] since multiple read-only actions are
allowed at the same time.
> (2) Can a writer interfere with a reader? Let writeFile :: Integer -> Char
> -> FileIO () write n times a given char to a file. What is then the result
> of:
> do
> (r1,h1) <- runFileIO p readEntireFile
> (r2,h2) <- runFileIO p (writeFile 5 'X')
> return (r2,r1)
> Does it yield ((),"abcde"), ((),"XXXXX"), (_|_,"abcde"), or _|_? What is
> the result when (r1,r2) is returned instead of (r2,r1)?
An important aim concerning my approach is that resulting states and results
of actions shall be independent of evaluation order. So swapping the
components of a result pair should never make a difference instead of
swapping the components, of course.
When execution comes to the point where the writeFile actions has to be
started, p is already opened for reading. So the second runFileIO would
return _|_ and the pattern matching would fail. Alas, I didn't think about
how to implement fail for those lazy I/O monads so far.
The problem here is, of course, that I just want to return two values and have
to use a lifted pair in order to do this while an unlifted pair would make
more sense. If we try to simulate the effect of using an unlifted pair, we
probably would use a lazy pattern for matching. If you would do so, r2 and
h2 would just become _|_ so that the result of the whole action would just be
(_|_,"abcde").
A better solution would be to let runFileIO throw an exception or return
values of type Maybe (result,FileIOHandle).
Thanks for asking this question. It reveals problems I didn't think about so
far.
> (3) One of the advantages of an explicit environment passing scheme is that
> you get true functional behaviour of programs. As an example, in Clean you
> can write a function that tests the content of a file, and if successfull
> proceeds with the remainder, and otherwise with its argument file. (Clean
> code ahead):
> parseInt :: Int File -> (Int,File)
> parseInt n file
>
> | ok && isDigit c = parseInt (n*10+d) file1
> | otherwise = (n,file)
>
> where (ok,c,file1) = sfreadc file
> d = toInt c - toInt '0'
> Does your scheme allow such kind of behavior?
Currently not. However, I think it would be possible by creating an
appropriate instance of MonadPlus for read-only actions. mzero would denote
some kind of failing and action1 `mplus` action2 would denote an action which
would first try to execute action1 and if action1 fails try to execute
action2 starting with the local state (file pointer position or whatever)
that was present before running action1. Failure of read actions etc. should
result in the action being equivalent to mzero. So parseInt would become
something like this:
parseInt :: Int -> FileReadIO Int
parseInt n =
(do
c <- readChar
if isDigit c
then parseInt (10 * n + (ord c - ord '0'))
else mzero)
`mplus` return n
Thanks for asking this question since it helps me to improve my idea.
> > An extended version of this approach shall also handle situations like
> > pure reading of files where not all read operations have to be carried
> > out if they are not needed to produce the desired result. In this case,
> > finishFileIO would just close the file without previously executing the
> > remainder of the file I/O action. The problem is that it cannot be
> > assured that as yet unevaluated parts of the result aren't evaluated
> > after exeuction of finishFileIO. Therefore, if evaluation after
> > finishing demands the execution of read operations these operations shall
> > not actually be executed but instead _|_ shall be returned.
>
> This scheme forces the programmer to carefully plan calls to finishFileIO.
> Let's assume that the readEntireFile is a pure reader of files, then the
> program fragment:
> do
> (r1,h1) <- runFileIO p readEntireFile
> finishFileIO h1
> ... computations that use r1 ...
> always use _|_ for r1. It is not always the case that
> do
> (r1,h1) <- runFileIO p readEntireFile
> ... computations that use r1 ...
> finishFileIO h1
> solves the problem, in particular when the computations that use r1 are
> pure functions. You'd have to "connect" r1 to the WorldIO monad before
> doing finishFileIO on h1.
Hmm, I wasn't aware of the fact that in such cases the result actually depends
on evaluation order, something which I wanted to avoid. However, if I choose
to not provide a finishFileIO for read-only actions and implement some kind
of implicit file closing when the whole file is read, it can be indeterminate
if a future runFileIO on the same file fails or not since the question if the
file is already closed or not might depend on evaluation order.
The only solution I can imagine at the moment is to let finishFileIO force the
execution of the remaining I/O also in case of read-only actions. But this
would make implementing something like getContents impossible. :-( Do you
have an idea for a better approach?
> How can you tell a function is a pure reader?
By its type.
> [...]
> Good luck with your thesis. I'd like to see the final result.
Thank you, that's nice. :-) Alas, the thesis will be in German so if you
don't understand German all you can do is to see if I will translate the
relevant parts into English. :-( Of course, the code will also tell a lot.
> Regards,
> Peter Achten
Best wishes,
Wolfgang Jeltsch
More information about the Haskell
mailing list