Imperative Notation
Pascal Serrarens
pascalrs@cs.kun.nl
Wed, 10 Feb 1999 10:47:48 +0100
Nick Kallen wrote:
> I'm going to sketch some really informal stuff that will hopefully spur some
> more concrete discussion and debate. (I'm going to completely ignore the
> structure of the IO libraries and just try to do this as simply and as
> clearly as possible.)
Ok, I read your examples and tried to see how far we can get using the things
Clean has to offer.
Of course, I cannot provide do-expression, because that is syntax, and I cannot
create new syntax.
First let's introduce the functions I am going to use in these examples. They
are all experimental and mostly derived from functions in the StdEnv.
readline :: !*(*ch a) !*env -> (!{#Char}, !*(*ch a), !*env)
| Receive ch & ThreadEnv env & toChar a
Reads a line from a stream (like a file, TCP channel, etc.)
writes :: !{#Char} !*(*ch a) !*env -> (!*(*ch a), !*env)
| Send ch & ThreadEnv env & fromChar a
Writes a string on a stream.
openFile :: !String !Int !*env -> (!*FILE, !*env) | ThreadEnv env
Opens a file.
class CloseChannel ch
where
close :: !*(*ch .a) !*env -> *env | ThreadEnv env
Closes any stream (including files)
I implemented some combinators which hide away the states completely. Note that
the readline/writes functions need both a stream (state) and an environment
(state). I wanted to hide both of them, to enable file access which is as
simple as possible.
First I tried to write some combinators which hide away the states completely:
:: ST ref a *env :== ref -> *(env -> *(a, ref, env))
:: ST` ref *env :== ref -> *(env -> *( ref, env))
(:\\) infixr 0 :: (ST` .ref *env) (ST .ref b *env) -> ST .ref b *env
(=\\) infixr 0 :: (ST .ref a *env) (a -> ST .ref b *env) -> ST .ref b *env
ret :: .a .ref *env -> (.a, .ref, *env)
(If you are interested in the code, you can ask me. I left them out for brievety)
Furthermore I implemented a function which starts a file sequence (monad).
withFile :: {#Char} Int (ST FILE a *env) -> ET a *env | ThreadEnv env
(ET is a environment transformer, like the IO monad, see below)
Now I could write a function like:
file_func1
= withFile "T.icl" FReadText (
writes "hello world" :\\
readline =\\ \cs1 ->
readline =\\ \cs2 ->
ret (cs1, cs2)
)
This is very nice and clean, but might be inconvenient when dealing with
more than one file simultanously.
For this, I wrote environment combinators:
:: *ET a *env :== env -> *(a, env)
:: *ET` *env :== env -> env
(:\) infixr 0 :: (ET` *env) (ET .a *env) -> ET .a *env
(=\) infixr 0 :: (ET .a *env) *(.a -> ET .b *env) -> ET .b *env
ret` :: .a *env -> (.a, *env)
Furthermore I needed a function which transforms a state transformer into
a environment transformer:
et :: (ST .ref .a *env) -> (.ref -> ET (.ref, .a) *env)
Now I can write a monadic function which manipulates two files:
file_func2 =
openFile "T.icl" FReadText =\ \f1 ->
openFile "T2.icl" FWriteText =\ \f2 ->
et readline f1 =\ \(f1, cs) ->
writes cs f2 =\ \f2 ->
close f1 :\
close f2 :\
ret` Void
As for the parallel composition. I implemented a parallel combinator, which
really evaluates two environment transformers concurrently (in Concurrent
Clean, of course):
(<|>) infix 0 :: (ET .a *TState) (ET .b *TState) -> (ET (.a, .b) *env)
| ThreadEnv env
(TState is the environment for threads, like World is for programs)
And I can put the two file functions in parallel:
file :: ET ( (String, String), Void ) *env | ThreadEnv env
files = file_func1 <|> file_func2
As you see there is no need to open the file system; this is hidden.
Please tell me what you think of this. Is it (apart from do-expression
like syntax) fantastic/wonderful/good/acceptable/improvable/bad/very
very very bad???
Pascal Serrarens.