line-based interactive program
hi, i'd like to know how to write simply a line-based interactive program, that is one with which you have a 'talk'. a simple filter-like line-based program ca be written as: -- a filter program process an entire input to yield some output type FilterProgram = [Line] -> [Line] but i don't knwo how to handle input/output such that the output can be printed before all the input exists (the user enter some lines, the program writes a response based on those lines, and so on) what's the simplest/most elegant approach ? thanks, minh thu
mt wrote:
hi,
i'd like to know how to write simply a line-based interactive program, that is one with which you have a 'talk'.
I would start with: main = do putStrLn "Please enter text (or press return to exit):" s <- getLine if s /= "" then do putStrLn s main else return ()
a simple filter-like line-based program ca be written as:
-- a filter program process an entire input to yield some output type FilterProgram = [Line] -> [Line]
Forget this, if it's not an (old) exercise Christian
Christian Maeder wrote:
-- a filter program process an entire input to yield some output type FilterProgram = [Line] -> [Line]
Forget this, if it's not an (old) exercise
Yes, people don't write lazy functional programs in Haskell any more. In the Era of Monadic Enlightenment, obfuscated imperative programming is the Way To Go. :-\ :-) However, for those who like to indulge the odd moment of nostalgia, in the Haskell 98 Prelude there is: interact :: (String -> String) -> IO () If this function does not work correctly with the Haskell implementation you use, do report the fault to the implementors. Colin R (purveyor of old exercises)
Hello Colin, Thursday, July 07, 2005, 4:33:39 PM, you wrote:
type FilterProgram = [Line] -> [Line]
CR> interact :: (String -> String) -> IO () and there is lines and unlines functions to do just what yopu need example: main = interact (unlines.filter(not.null).lines) -- Best regards, Bulat mailto:bulatz@HotPOP.com
Bulat Ziganshin wrote:
Hello Colin,
Thursday, July 07, 2005, 4:33:39 PM, you wrote:
type FilterProgram = [Line] -> [Line]
CR> interact :: (String -> String) -> IO ()
and there is lines and unlines functions to do just what yopu need
example: main = interact (unlines.filter(not.null).lines)
Could you also insert a prompt that is shown before the lines are read? (The first prompt seems to be tricky assuming line buffering ) Christian
Hello Christian, Thursday, July 07, 2005, 6:55:13 PM, you wrote: CM> Could you also insert a prompt that is shown before the lines are read? CM> (The first prompt seems to be tricky assuming line buffering ) import System.IO main = do hSetBuffering stdin LineBuffering hSetBuffering stdout LineBuffering interact (unlines.filter(not.null).lines.('>':)) -- Best regards, Bulat mailto:bulatz@HotPOP.com
Christian Maeder wrote:
Could you also insert a prompt that is shown before the lines are read? (The first prompt seems to be tricky assuming line buffering )
If line-buffering is assumed or imposed, of course it prevents the programming of interactive applications where the units of input or output are less than a line! However, there is no need to build line-buffering into the system, because it is easily defined in Haskell: buffer xs = foldl const xs xs lineBuffered = map buffer . lines Regards Colin R
Colin Runciman wrote:
output are less than a line! However, there is no need to build line-buffering into the system, because it is easily defined in Haskell:
buffer xs = foldl const xs xs
I don't find it this easy nor a good programming practise. My interaction depends on the (subtle order of) evaluation of a pure and total function? Cheers Christian
Christian,
buffer xs = foldl const xs xs I don't find it this easy nor a good programming practise.
I don't see why you should think it hard to define a function like 'buffer'. The whole purpose of foldl is to encapsulate accumulation. It demands the full spine of its list argument to produce any result, and that's what we want. And we don't want any extra computation, just the list argument itself; hence 'const' and 'xs'.
Your implicitly proposed "programming practise" is actually *not to program* buffering at all! Just have it as an ad hoc IO directive, programmed less concisely and no more clearly in a low-level library or run-time system. How is that better?
My interaction depends on the (subtle order of) evaluation of a pure and total function?
Pure, yes; total, no.
Many important things depend on order of evaluation in lazy programs: for example, whether they compute a well-defined value at all! The interleaving of demand in the argument of a function with computational progress in its result seems a perfectly natural view of interaction in a lazy functional language. This sort of interaction is what actually happens when your function applications are evaluated whether you exploit it or not. I embrace it as part of lazy functional programming; you prefer an appeal to something extraneous. [I am conscious that we are using bandwidth on the main Haskell mailing list for this little discussion -- perhaps we are about done, but if not perhaps we should mail each other direct.] Regards Colin R
My interaction depends on the (subtle order of) evaluation of a pure and total function?
Pure, yes; total, no.
Many important things depend on order of evaluation in lazy programs: for example, whether they compute a well-defined value at all! The interleaving of demand in the argument of a function with computational progress in its result seems a perfectly natural view of interaction in a lazy functional language. This sort of interaction is what actually happens when your function applications are evaluated whether you exploit it or not. I embrace it as part of lazy functional programming; you prefer an appeal to something extraneous.
It seems to me that this sort of thing is why haskell is difficult to compile to efficient code. I have the impression that relaxed semantics wouldn't hurt 99% of programs while make the compiler-writer job easier. The only disadvantage is that tricks like the above one wouldn't work any more. Another point is, imho, most haskell programmers don't grasp the exact lazyness semantics; they think of lazyness as "evaluation order is up to the compiler", not the precise graph-reduction thing. Hence, haskell would perhaps be better off with more "fuzzy" semantics, since it would match the intuition of people, and allow more optimized code. Those ideas have been explored before, if I'm correct in the "optimistic evaluation" project, and Eager haskell. Yet, I'm wondering what's the current opinion of the haskell "gurus" on the subject.
[I am conscious that we are using bandwidth on the main Haskell mailing list for this little discussion -- perhaps we are about done, but if not perhaps we should mail each other direct.]
The subject is very interesting to me, and I suspect many others, so feel free. Cheers, JP. [1] Eager haskell http://csg.csail.mit.edu/projects/?action=viewProject&projectID=5&projectGro... [2] Optimistic evaluation http://www.cambridge.intel-research.net/~rennals/icfp2003.pdf
It seems to me that this sort of thing is why haskell is difficult to compile to efficient code. I have the impression that relaxed semantics wouldn't hurt 99% of programs while make the compiler-writer job easier. The only disadvantage is that tricks like the above one wouldn't work any more.
Another point is, imho, most haskell programmers don't grasp the exact lazyness semantics; they think of lazyness as "evaluation order is up to the compiler", not the precise graph-reduction thing.
Hence, haskell would perhaps be better off with more "fuzzy" semantics, since it would match the intuition of people, and allow more optimized code. Those ideas have been explored before, if I'm correct in the "optimistic evaluation" project, and Eager haskell. Yet, I'm wondering what's the current opinion of the haskell "gurus" on the subject.
I'm not sure that `lots of people think that the semantics are fuzzy' is a good reason to change the semantics to be as such. I would assert that the more rigid and tidy you make the semantics, the easier it becomes to prove nice properties etc. and from what I've seen, Haskell's semantics are already just a tiny bit too `fuzzy' to do this kind of thing.
[I am conscious that we are using bandwidth on the main Haskell mailing list for this little discussion -- perhaps we are about done, but if not perhaps we should mail each other direct.]
The subject is very interesting to me, and I suspect many others, so feel free.
Agreed - I was very much enjoying watching this thread. Bobb
On Jul 8, 2005, at 10:20 AM, Jean-Philippe Bernardy wrote (in an exchange with Colin Runciman):
My interaction depends on the (subtle order of) evaluation of a pure and total function?
Pure, yes; total, no.
Many important things depend on order of evaluation in lazy programs: for example, whether they compute a well-defined value at all! ... It seems to me that this sort of thing is why haskell is difficult to compile to efficient code. I have the impression that relaxed semantics wouldn't hurt 99% of programs while make the compiler-writer job easier. The only disadvantage is that tricks like the above one wouldn't work any more.
Another point is, imho, most haskell programmers don't grasp the exact lazyness semantics; they think of lazyness as "evaluation order is up to the compiler", not the precise graph-reduction thing.
Hence, haskell would perhaps be better off with more "fuzzy" semantics, since it would match the intuition of people, and allow more optimized code. Those ideas have been explored before, if I'm correct in the "optimistic evaluation" project, and Eager haskell. Yet, I'm wondering what's the current opinion of the haskell "gurus" on the subject.
Be careful. Both Eager Haskell and optimistic evaluation went to some pains to preserve the exact same non-strict semantics as lazy implementations of Haskell. Eager Haskell had particular trouble with lazy computations (especially infinite streams): it computed quite a bit of extraneous gunk before noticing that it was wasting time and returning to useful computation. This came up more often than I initially assumed, though most idiomatic uses were easy to eliminate (by substituting "numberListFrom n" for "zip [n..]" for example). By contrast, pH has the "fuzzy" semantics you seem to imagine---it just runs eagerly and non-strictly. A number of Haskell idioms (anything involving where clauses springs to mind) don't work quite the way you'd expect in this setting, and you have to be relatively careful about code motion during program optimization. -Jan-Willem Maessen
[I am conscious that we are using bandwidth on the main Haskell mailing list for this little discussion -- perhaps we are about done, but if not perhaps we should mail each other direct.]
The subject is very interesting to me, and I suspect many others, so feel free.
Cheers, JP.
[1] Eager haskell http://csg.csail.mit.edu/projects/? action=viewProject&projectID=5&projectGroup=Programming%20Languages
[2] Optimistic evaluation http://www.cambridge.intel-research.net/~rennals/icfp2003.pdf _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Fri, Jul 08, 2005 at 02:51:11PM +0100, Colin Runciman wrote:
My interaction depends on the (subtle order of) evaluation of a pure and total function?
Pure, yes; total, no.
Many important things depend on order of evaluation in lazy programs: for example, whether they compute a well-defined value at all! The interleaving of demand in the argument of a function with computational progress in its result seems a perfectly natural view of interaction in a lazy functional language. This sort of interaction is what actually happens when your function applications are evaluated whether you exploit it or not. I embrace it as part of lazy functional programming; you prefer an appeal to something extraneous.
It is one thing to embrace lazy evaluation order, and another to embrace lazy IO (implemented using unsafeInterleaveIO). As a relative newcomer to Haskell, I got the impression that the "interact" style was always a hack, discarded for good reason in favor of the IO monad. Is there a strong case for interact? Andrew
Hello Andrew, Friday, July 08, 2005, 8:43:02 PM, you wrote: AP> It is one thing to embrace lazy evaluation order, and another to embrace AP> lazy IO (implemented using unsafeInterleaveIO). As a relative newcomer AP> to Haskell, I got the impression that the "interact" style was always a AP> hack, discarded for good reason in favor of the IO monad. Is there a AP> strong case for interact? interact are very good for filter-like programs, but i think iy's unadequate for inetractive programs. i use "main = interact (unlines...lines)" often enough -- Best regards, Bulat mailto:bulatz@HotPOP.com
Andrew Pimlott wrote:
It is one thing to embrace lazy evaluation order, and another to embrace lazy IO (implemented using unsafeInterleaveIO). As a relative newcomer to Haskell, I got the impression that the "interact" style was always a hack, discarded for good reason in favor of the IO monad. Is there a strong case for interact?
Why invent a new feature (IO monad) if you can do IO already based on lazy evaluation? It is no argument against "interact" that all Haskell systems use unsafeInterleaveIO to implement it. Any kind of IO needs some primitive, you could just as well have interact directly as primitive. In fact, unsafeInterleaveIO shows up limitations of the IO monad. Without this strange primitive (what is actually unsafe about it?) you cannot simulate lazy IO within the IO monad. Another example is the previously discussed line buffering of input. If this were not a primitive in the IO monad, how would you define it in Haskell without some nasty hacks (a top-level IORef springs to my mind)? The IO monad certainly has advantages with respect to modularity. A shame that you cannot freely mix lazy IO and monadic IO in a program. I find it sad that the IO monad has so widely been accepted that nobody seems to search for better alternatives any more. Ciao, Olaf
Am Freitag, 8. Juli 2005 19:21 schrieb Olaf Chitil:
[...]
In fact, unsafeInterleaveIO shows up limitations of the IO monad. Without this strange primitive (what is actually unsafe about it?)
unsafeInterleaveIO doesn't break referential transparency, right? I suppose, it is unsafe in the sense that what happens upon execution depends on the order of evaluation.
[...]
Ciao, Olaf
Best wishes, Wolfgang
On Friday 08 Jul 2005 6:21 pm, Olaf Chitil wrote:
If this were not a primitive in the IO monad, how would you define it in Haskell without some nasty hacks (a top-level IORef springs to my mind)?
Do you mean using a top-level IORef is a nasty hack, or you refering to the nasty hack we have to use to create one? If it's the former then please explain why this is a nasty hack. (and thanks for not using the term "global variable" BTW :-) If you care to answer, you might like to look at this wiki page first.. http://haskell.org/hawiki/GlobalMutableState Sorry to burden you with this, but I've decided to make it my mission in life to challenge such assertions (well at least whenever one of my other missions in life doesn't distract me). Regards -- Adrian Hey
Am Freitag, 8. Juli 2005 18:43 schrieb Andrew Pimlott:
[...]
It is one thing to embrace lazy evaluation order, and another to embrace lazy IO (implemented using unsafeInterleaveIO). As a relative newcomer to Haskell, I got the impression that the "interact" style was always a hack, discarded for good reason in favor of the IO monad. Is there a strong case for interact?
unsafeInterleaveIO is useful roughly because of the same resons, lazy evaluation is useful. For example, you don't need to overspecify the relative order in which certain I/O primitives occur. The problem with it is, of course, that it's unsafe. As part of my diploma thesis, I'm working on a small collection of modules which provides safe I/O interleaving. The key point is to split the state of the world since I/O on different parts of the world can be interleaved arbitrarily. If someone is interested, I can post more details.
Andrew
Best wishes, Wolfgang
Hello Wolfgang, Friday, July 08, 2005, 11:55:48 PM, you wrote: WJ> As part of my diploma thesis, I'm working on a small collection of modules WJ> which provides safe I/O interleaving. The key point is to split the state of WJ> the world since I/O on different parts of the world can be interleaved WJ> arbitrarily. If someone is interested, I can post more details. yes, i am interested. i feel that this would be very interesting and useful for real programs -- Best regards, Bulat mailto:bulatz@HotPOP.com
Am Samstag, 9. Juli 2005 08:31 schrieb Bulat Ziganshin:
Hello Wolfgang,
Friday, July 08, 2005, 11:55:48 PM, you wrote:
As part of my diploma thesis, I'm working on a small collection of modules which provides safe I/O interleaving. The key point is to split the state of the world since I/O on different parts of the world can be interleaved arbitrarily. If someone is interested, I can post more details.
yes, i am interested. i feel that this would be very interesting and useful for real programs
Hello, the idea is to have different monads for I/O on different resources. A simple example is to have the two monads WorldIO and FileIO and a type FileIOHandle. A file is a part of the world. You have the following functions: readChar :: FileIO Char writeChar :: Char -> FileIO () runFileIO :: FilePath -> FileIO a -> WorldIO (a, FileIOHandle) finishFileIO :: FileIOHandle -> WorldIO () readChar and writeChar should be self-explanatory. At first, runFileIO does nothing instead of opening the file and returning the result. Whenever parts of the first component of the result pair are evaluated, as much readChar and writeChar actions of the file I/O action are executed as are needed to produce the desired parts of the result. finishFileIO executes the remainder of the file I/O and closes the file afterwards. 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. I also plan to provide a general framework for working on parts of the state interleaved with working on the remainder of the state. The framework shall be hierarchical in the sense that you cannot just work on parts of the world but also on parts of parts of the world and so on. It will probably use multi-parameter classes to describe which thing is part of which other thing. When my diploma thesis and the corresponding talk are finished (which will probably at the end of September), I may post a more detailed description on this list and also provide some code. Best wishes, Wolfgang
Hello Wolfgang, Saturday, July 09, 2005, 7:01:06 PM, you wrote:
As part of my diploma thesis, I'm working on a small collection of modules which provides safe I/O interleaving. The key point is to split the state of the world since I/O on different parts of the world can be interleaved arbitrarily. If someone is interested, I can post more details.
yes, i am interested. i feel that this would be very interesting and useful for real programs
WJ> the idea is to have different monads for I/O on different resources. A simple WJ> example is to have the two monads WorldIO and FileIO and a type FileIOHandle. WJ> A file is a part of the world. You have the following functions: WJ> readChar :: FileIO Char WJ> writeChar :: Char -> FileIO () WJ> runFileIO :: FilePath -> FileIO a -> WorldIO (a, FileIOHandle) WJ> finishFileIO :: FileIOHandle -> WorldIO () WJ> readChar and writeChar should be self-explanatory. At first, runFileIO does WJ> nothing instead of opening the file and returning the result. Whenever parts WJ> of the first component of the result pair are evaluated, as much readChar and WJ> writeChar actions of the file I/O action are executed as are needed to WJ> produce the desired parts of the result. finishFileIO executes the remainder WJ> of the file I/O and closes the file afterwards. sorry, it is probably not what i'm think about. as i can see, you are provide safe equivalent of interleaveIO, while i'm think about approach that simplifies creating of I/O and mutable-state programs, something like: string <- [readChar h, readChar h] which automatically perform I/O actions `readChar h` in I/O monad and then uses their results as in ordinal computation. this will allow "do" syntax to more close imitate imperative languages -- Best regards, Bulat mailto:bulatz@HotPOP.com
Am Samstag, 9. Juli 2005 18:17 schrieb Bulat Ziganshin:
[...]
sorry, it is probably not what i'm think about. as i can see, you are provide safe equivalent of interleaveIO,
Not really. An important difference between my solution and unsafeInterleaveIO is that with unsafeInterleaveIO the whole "interleaved" action is executed as soon as something of its result is demanded while with my approach the action is just executed as far as it is necessary to provide the demanded data.
while i'm think about approach that simplifies creating of I/O and mutable-state programs, something like:
string <- [readChar h, readChar h]
which automatically perform I/O actions `readChar h` in I/O monad and then uses their results as in ordinal computation. this will allow "do" syntax to more close imitate imperative languages
I don't really understand your aim at the moment. Could you please elaborate a bit? Best wishes, Wolfgang
At 05:01 PM 7/9/2005, Wolfgang Jeltsch wrote:
[..] Hello,
the idea is to have different monads for I/O on different resources. A simple example is to have the two monads WorldIO and FileIO and a type FileIOHandle. A file is a part of the world. You have the following functions:
readChar :: FileIO Char writeChar :: Char -> FileIO () runFileIO :: FilePath -> FileIO a -> WorldIO (a, FileIOHandle) finishFileIO :: FileIOHandle -> WorldIO ()
readChar and writeChar should be self-explanatory. At first, runFileIO does nothing instead of opening the file and returning the result. Whenever parts of the first component of the result pair are evaluated, as much readChar and writeChar actions of the file I/O action are executed as are needed to produce the desired parts of the result. finishFileIO executes the remainder of the file I/O and closes the file afterwards.
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 where readEntireFile reads the entire file and returns it as a string. I can imagine several results: [a,a], [a,b], [a,_|_], [_|_,_|_], _|_. (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)? (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?
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. How can you tell a function is a pure reader?
I also plan to provide a general framework for working on parts of the state interleaved with working on the remainder of the state. The framework shall be hierarchical in the sense that you cannot just work on parts of the world but also on parts of parts of the world and so on. It will probably use multi-parameter classes to describe which thing is part of which other thing.
When my diploma thesis and the corresponding talk are finished (which will probably at the end of September), I may post a more detailed description on this list and also provide some code.
Good luck with your thesis. I'd like to see the final result. Regards, Peter Achten
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
At 12:02 PM 7/12/2005, Wolfgang Jeltsch wrote:
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? Sure, forgive my rusty Haskell...
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.
['a','a'] is a good result in case of multiple readers. But how do you distinguish between read-only I/O and write-permitted I/O? In your example you introduced:
readChar :: FileIO Char writeChar :: Char -> FileIO ()
Both belong to the FileIO monad. This also relates to the question at the bottom of the previous e-mail.
(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. Here you introduce a FileReadIO monad. Is this the way you intend to distinguish between read-only actions and write-permitted actions? That seems okay.
Minor detail: you use readChar :: FileIO Char within FileReadIO monad. That won't type check.
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? Hmm (thinking...). No.
How can you tell a function is a pure reader?
By its type. See above comments: unless you use a separate type for readers like FileReadIO, or provide additional information to runFileIO I don't think you can distinguish them.
[...]
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. Please don't go through the trouble of translating to English. My German is fairly rusty, but I should be able to manage. We're neighbours after all.
Regards, Peter Achten
Best wishes, Wolfgang Jeltsch _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Am Donnerstag, 14. Juli 2005 13:17 schrieben Sie:
[...]
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.
['a','a'] is a good result in case of multiple readers. But how do you distinguish between read-only I/O and write-permitted I/O? In your example
you introduced:
readChar :: FileIO Char writeChar :: Char -> FileIO ()
Both belong to the FileIO monad. This also relates to the question at the bottom of the previous e-mail.
FileIO was the type from a previous approach where I didn't intend to distinguish between write-permitted and read-only I/O. The read-only thing which I mentioned later in my mail was an enhancement to this approach where write-permitted and read-only actions use different types.
[...]
Here you introduce a FileReadIO monad. Is this the way you intend to distinguish between read-only actions and write-permitted actions? That seems okay.
Yes, FileReadIO denotes read-only actions whereas write-permitted actions are denoted by a different monad.
Minor detail: you use readChar :: FileIO Char within FileReadIO monad. That won't type check.
In the advanced approach, readChar would have type FileReadIO Char.
[...]
Best wishes, Wolfgang Jeltsch
On Fri, Jul 08, 2005 at 09:55:48PM +0200, Wolfgang Jeltsch wrote:
As part of my diploma thesis, I'm working on a small collection of modules which provides safe I/O interleaving. The key point is to split the state of the world since I/O on different parts of the world can be interleaved arbitrarily. If someone is interested, I can post more details.
Is it something similar to Clean's approach to IO? Best regards Tomasz
Am Samstag, 9. Juli 2005 15:25 schrieben Sie:
On Fri, Jul 08, 2005 at 09:55:48PM +0200, Wolfgang Jeltsch wrote:
As part of my diploma thesis, I'm working on a small collection of modules which provides safe I/O interleaving. The key point is to split the state of the world since I/O on different parts of the world can be interleaved arbitrarily. If someone is interested, I can post more details.
Is it something similar to Clean's approach to IO?
It is in fact "inspired" by Clean's I/O approch whereby the implementation differs significantly from Clean's because Haskell has no uniqueness type system. :-) See my following answer to Bulat Ziganshin's mail.
Best regards Tomasz
Best wishes, Wolfgang
Christian Maeder wrote:
Colin Runciman wrote:
buffer xs = foldl const xs xs
I don't find it this easy nor a good programming practise.
My interaction depends on the (subtle order of) evaluation of a pure and total function?
I would not think so much about the operational evaluation order, but about the denotational value of functions. In a non-strict language functions always also accept partially defined arguments (including bottom _|_). What a function returns for partially defined arguments tells you how it can be used in an interactive program. So: buffer _|_ = _|_ buffer (a1:_|_) = _|_ buffer (a1:a2:_|_) = _|_ ... buffer (a1:a2:...:an:[]) = a1:a2:...:an:[] Ciao, Olaf
participants (13)
-
Adrian Hey -
Andrew Pimlott -
Bulat Ziganshin -
Christian Maeder -
Colin Runciman -
Jan-Willem Maessen -
Jean-Philippe Bernardy -
mt -
Olaf Chitil -
Peter Achten -
Thomas Davie -
Tomasz Zielonka -
Wolfgang Jeltsch