RE: [Haskell] threading mutable state through callbacks
On 08 October 2004 19:18, Sven Panne wrote:
Jules Bean wrote:
[...] Unfortunately, it's not going to work. It's not going to work because some of the procedures take callbacks, and the callbacks are values of type IO (). I can see two solutions to this:
a) revert to using an IORef [...] b) write the callbacks as values of type StateT Env IO () [...]
or
c) Give up any hope of clean semantics and simply use a common hack like:
{-# NOINLINE myGlobalVar #-} myGlobalVar :: IORef Int myGlobalVar = unsafePerformIO (newIORef 0)
My GLUT binding does it happily, as does GHC itself, [snip]
I'd like to add that while the implementation might be a little unsafe, there's no problem in principle with the semantics of top-level IORefs. We could add such a thing as a GHC extension, but it would be nice if it were an instance of a more general-purpose extension. Cheers, Simon
On Monday 11 Oct 2004 4:03 pm, Simon Marlow wrote:
On 08 October 2004 19:18, Sven Panne wrote:
c) Give up any hope of clean semantics and simply use a common hack like:
{-# NOINLINE myGlobalVar #-} myGlobalVar :: IORef Int myGlobalVar = unsafePerformIO (newIORef 0)
My GLUT binding does it happily, as does GHC itself, [snip]
I'd like to add that while the implementation might be a little unsafe, there's no problem in principle with the semantics of top-level IORefs. We could add such a thing as a GHC extension, but it would be nice if it were an instance of a more general-purpose extension.
I found myself doing this kind of thing quite a bit recently for FFI binding. It would be nice have some solution such that the compiler was guaranteed to be aware of the semantic problems and we didn't have to rely on unsafePerformIO, NOINLINE etc hackery. I don't know what more general-purpose extension you have in mind, but couldn't you just borrow from do syntax at the top level, something like this..
myThing :: Thing myThing <- newThing
(where newThing :: IO Thing) Regards -- Adrian Hey
On Tuesday 12 October 2004 12:23, Adrian Hey wrote:
I don't know what more general-purpose extension you have in mind, but couldn't you just borrow from do syntax at the top level
I think that the problem is with the order of execution of these bindings. For example ghci supports top-level "let x <- something" declarations, but the haskell compiler does not have to respect any order of execution, which would instead be forced. The general purpose extension could however require the same proof obligations of unsafeInterleaveIO, but how do we deal with x <- someAction y <- someAction(x) V. -- Bow down before the one you serve, you're going to get what you deserve. [Nine Inch Nails]
On Tuesday 12 Oct 2004 1:44 pm, Vincenzo Ciancia wrote:
On Tuesday 12 October 2004 12:23, Adrian Hey wrote:
I don't know what more general-purpose extension you have in mind, but couldn't you just borrow from do syntax at the top level
I think that the problem is with the order of execution of these bindings. For example ghci supports top-level "let x <- something" declarations, but the haskell compiler does not have to respect any order of execution, which would instead be forced. The general purpose extension could however require the same proof obligations of unsafeInterleaveIO, but how do we deal with
x <- someAction y <- someAction(x)
I would say keep things as they currently are with the unsafePerformIO solution, I.E. Order unspecified, the action that creates a particular top level thing is executed only once, when the value of thing is demanded (perhaps not at all). If ordering is significant (due to important side effects say) then it can be controlled with `seq` I think. But I think something ought to be done about it. Having to use unsafePerformIO to do something that ought to be perfectly safe is just embarrassing I think :-) There's also the type security issues that Marcin was talking about, but I don't see why that can't be addressed with appropriate constraints which are enforced at compile time. Regards -- Adrian Hey
On 12 Oct 2004, at 14:08, Adrian Hey wrote:
x <- someAction y <- someAction(x)
I would say keep things as they currently are with the unsafePerformIO solution, I.E. Order unspecified, the action that creates a particular top level thing is executed only once, when the value of thing is demanded (perhaps not at all).
Also consider the case of z = someAction(y) Here z is a value outside the IO monad, calculated using a function outside the IO monad, based on a value (y) which also lies outside the IO monad... What sane semantics will explain when the actions which led to the value y should be taken? I think what people are trying to suggest is an 'initialization phase' in the IO monad, which takes place "before" the pure functions are defined. At compile time, conceptually. I don't have any clear idea how that should be made precise. Jules
Jules Bean <jules@jellybean.co.uk> writes:
I think what people are trying to suggest is an 'initialization phase' in the IO monad, which takes place "before" the pure functions are defined.
If it was done before, what could you use to specify initial value of such a variable? Only literals? Constructors? Named constants? Results of arithmetic operations? Results of arbitrary functions? The well-definedness of a global IORef relies on the fact that creation of an IORef doesn't have visible side effects, so it doesn't matter when it happens, as long as it happens once between the variable is used. An arbitrary IO computation doesn't have this property. Unfortunately having only IORefs would be limiting. You couldn't legally make stdin/stdout/stderr for example. Now they must rely on compiler magic for something which doesn't seem to need magic by nature (allocation of some MVars and other objects, not doing actual I/O). -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
On 12 Oct 2004, at 18:59, Marcin 'Qrczak' Kowalczyk wrote:
Jules Bean <jules@jellybean.co.uk> writes:
I think what people are trying to suggest is an 'initialization phase' in the IO monad, which takes place "before" the pure functions are defined.
If it was done before, what could you use to specify initial value of such a variable? Only literals? Constructors? Named constants? Results of arithmetic operations? Results of arbitrary functions?
Yes, that's what I was trying to get at. But you said it much better...
The well-definedness of a global IORef relies on the fact that creation of an IORef doesn't have visible side effects, so it doesn't matter when it happens, as long as it happens once between the variable is used. An arbitrary IO computation doesn't have this property.
Indeed. There are other hacks you can think of, though, if IORefs are your only goal. For example, something like getIORef :: Int -> a -> IORef a or getIORef :: String -> a -> IORef a (importantly, it's IORef, not IO IORef.) But this isn't great for programmer convenience, since there's nothing to help him discipline the use of particular variables. All we really need is a 'unique value monad' to give us unique values to be keys for IORefs (or whatever else), and allow this monad to be accessible at the top level. Conceptually this monad is commutative, so ordering of the actions doesn't matter. Jules
On Tuesday 12 Oct 2004 6:28 pm, Jules Bean wrote:
On 12 Oct 2004, at 14:08, Adrian Hey wrote:
x <- someAction y <- someAction(x)
I would say keep things as they currently are with the unsafePerformIO solution, I.E. Order unspecified, the action that creates a particular top level thing is executed only once, when the value of thing is demanded (perhaps not at all).
Also consider the case of
z = someAction(y)
Here z is a value outside the IO monad, calculated using a function outside the IO monad, based on a value (y) which also lies outside the IO monad... What sane semantics will explain when the actions which led to the value y should be taken?
I've never really understood what people mean by things being "inside" and "outside" the IO monad :-( Assuming from Vincenzos original example that .. someAction :: SomeType -> IO Thing then in your example.. z :: IO Thing So z is a perfectly ordinary value. The someAction that creates y will be executed only once, as soon as the value of y is needed, which (assuming someAction is strict) will be as soon as the value of z is needed, I.E. "Whenever" The only real insanity with the current situation is the loss of referential transparency implied by the use of unsafePerformIO, which is why various pragma hacks and compiler switches need to be used (in order to prevent inappropriate substitutions). What I want to do is to make this or something similar "official", so the compiler really knows not to do this inappropriate subsitution in any event, even if the programmer forgot to use the necessary pragmas or compiler flags, or just didn't understand why they are needed. This still leaves the uncertainty of exactly when the creation occurs, but this is a minor issue IMO. We have the same problem with finalisers and any concurrent code. Either it just doesn't matter, or if it does then it's the programmers responsibility to use existing mechanisms (such as seq) to control things.
I think what people are trying to suggest is an 'initialization phase' in the IO monad, which takes place "before" the pure functions are defined. At compile time, conceptually. I don't have any clear idea how that should be made precise.
I can't speak for what others are trying to suggest, but that isn't what I have in mind. What I want is non-hack mechanism for creation of arbitrarily complex "things with identity" at the top level. I guess you could arrange that all such things were constructed at run time before executing main, but I don't see any real advantage in that. Regards -- Adrian Hey
On Tuesday 12 October 2004 21:25, Adrian Hey wrote:
I've never really understood what people mean by things being "inside" and "outside" the IO monad :-(
Inside the IO monad means "correctly sequenced together with other IO operations which are inside the IO monad". It's called "inside" since people (me at least) view the IO monad as a one-way thingy where you have only an entry point (the main function), so you can either be "inside" and correctly sequenced, or "outside" like an unsafeInterleaveIO computation.
Assuming from Vincenzos original example that ..
someAction :: SomeType -> IO Thing
then in your example..
z :: IO Thing
So z is a perfectly ordinary value. The someAction that creates y will be executed only once, as soon as the value of y is needed, which (assuming someAction is strict) will be as soon as the value of z is needed, I.E. "Whenever"
The objection was for cases like x :: Int <- someIOAction y :: Int = x + 1 Note that both have Int type but the second is a pure value while the first is the result of a computation. I think that the easy way would be to allow a keyword like a top-level let, where a program like let x1 <- action1 ... let xn <- actionn main = other_actions is equivalent to main = do x1 <- action1 ... xn <- actionn other_actions top-level "ordinary" bindings can't see x1...xn, and order of evaluation is imposed. Or else you can simply say that NO binding, even x1...xn can see the others. This could be done with a special keyword and source-code preprocessing; ok, I am sorry for such a naive point of view - I mean the whole post :) V.
On Tuesday 12 Oct 2004 8:43 pm, Vincenzo Ciancia wrote:
The objection was for cases like
x :: Int <- someIOAction y :: Int = x + 1
Note that both have Int type but the second is a pure value while the first is the result of a computation.
Well obviously if people are going to use it stupidly (I.E. the value returned by someIOAction is dependent on when it occurs) that might be a bug. But there's really no guarantee of "sane" behaviour with anything that's done in the via the IO monad (finalisers, forkIO..), other than what the programmer ensures by writing bug free code. So I dunno why we should insist on higher standards of sanity in this case. But there is a real fundamental problem with the only current alternative (use of wierd library non-functions like unsafePerformIO), in that the compiler itself cannot be relied upon to interpret the program correctly and generate the code the programmer intended (at least not unless the programmer understands the dangers and supplies necessary flags & pragmas). If I have.. myThing = unsafePerformIO newThing myOtherThing = unsafePerformIO newThing Then conventional referential transparency means that the compiler should be able to freely interchange (myThing),(myOtherThing) and (unsafePerformIO newThing) anywhere it likes. Clearly a problem if they're IORefs or similar. By adding something like the myThing <- newThing at the top level this danger could be eliminated. Certainly abuse is still possible, but I don't care about that. (If some folk want to be that stupid it's fine with me :-)
I think that the easy way would be to allow a keyword like a top-level let, where a program like
let x1 <- action1 ... let xn <- actionn
main = other_actions
is equivalent to
main = do x1 <- action1 ... xn <- actionn other_actions
top-level "ordinary" bindings can't see x1...xn, and order of evaluation is imposed.
Unfortunately, in this case the whole point of what people are trying to do with unsafePerformIO is to allow these things to be visible at the top level :-) Regards -- Adrian Hey
On Wednesday 13 October 2004 00:00, Adrian Hey wrote:
Unfortunately, in this case the whole point of what people are trying to do with unsafePerformIO is to allow these things to be visible at the top level :-)
Sometimes I get too much involved in what I think about, and forget the original goal :) A little _too_ naive, it seems, I apologize. So it's like the original idea, that using these toplevel IO bindings one has to impose an order of evaluation over all program bindings, which surely is against the current meaning of haskell programs, e.g. if I say conf <- readMyConfFile init = fn conf people would agree that the correct meaning is to first evaluate all of the IO bindings and then the rest of the program: x1 <- a1 ... xn <- an v1 = expr1 ... vn = exprn main = action should be equivalent to main = do x1 <- a1 ... xn <- an let v1 = expr1 ... vn = exprn in action This would not change the meaning of a standard haskell program I think (but I am not an expert as you see). Am I wrong? Could this be done with a preprocessor? Why not? V.
Vincenzo Ciancia wrote:
Unfortunately, in this case the whole point of what people are trying to do with unsafePerformIO is to allow these things to be visible at the top level :-)
Sometimes I get too much involved in what I think about, and forget the original goal :) A little _too_ naive, it seems, I apologize. So it's like the original idea, that using these toplevel IO bindings one has to impose an order of evaluation over all program bindings, which surely is against the current meaning of haskell programs, e.g. if I say
conf <- readMyConfFile init = fn conf
people would agree that the correct meaning is to first evaluate all of the IO bindings and then the rest of the program:
x1 <- a1 ... xn <- an
v1 = expr1 ... vn = exprn
main = action
should be equivalent to
main = do x1 <- a1 ... xn <- an let v1 = expr1 ... vn = exprn in action
This would not change the meaning of a standard haskell program I think (but I am not an expert as you see). Am I wrong?
In the former, the variables have global scope, and may be exported from the module. Also, what if you do this in a module other than Main? -- Glynn Clements <glynn.clements@virgin.net>
On 12 Oct 2004, at 20:25, Adrian Hey wrote:
On Tuesday 12 Oct 2004 6:28 pm, Jules Bean wrote:
On 12 Oct 2004, at 14:08, Adrian Hey wrote:
x <- someAction y <- someAction(x)
I would say keep things as they currently are with the unsafePerformIO solution, I.E. Order unspecified, the action that creates a particular top level thing is executed only once, when the value of thing is demanded (perhaps not at all).
Also consider the case of
z = someAction(y)
Here z is a value outside the IO monad, calculated using a function outside the IO monad, based on a value (y) which also lies outside the IO monad... What sane semantics will explain when the actions which led to the value y should be taken?
I've never really understood what people mean by things being "inside" and "outside" the IO monad :-(
Assuming from Vincenzos original example that ..
someAction :: SomeType -> IO Thing
then in your example..
z :: IO Thing
Yes, I wrote it wrong. I meant z = someFunction(y) 'Inside the IO Monad' means having some type IO a. Outside it means having a type not of this form. At least, that's what I meant. [Vincent's explanation is another way of looking at it]
This still leaves the uncertainty of exactly when the creation occurs, but this is a minor issue IMO. We have the same problem with finalisers and any concurrent code. Either it just doesn't matter, or if it does then it's the programmers responsibility to use existing mechanisms (such as seq) to control things.
It does matter for general IO operations at the top level. Order doesn't matter in in any essential way for the particular case of newIORef, no.
What I want is non-hack mechanism for creation of arbitrarily complex "things with identity" at the top level. I guess you could arrange that all such things were constructed at run time before executing main, but I don't see any real advantage in that.
Now it's my turn not to understand you. What is a "thing with identity"? Jules
On Tuesday 12 Oct 2004 10:19 pm, Jules Bean wrote:
It does matter for general IO operations at the top level.
All I'm aiming for is to disambiguate programs (avoid the use of unsafePerformIO) in such a way that the programmer at least stands a reasonable chance of getting it right without compiler optimisations screwing things up.
Order doesn't matter in in any essential way for the particular case of newIORef, no.
Or for a great many other IO operations that a programmer may wish to use, though certainly not all that he could define (if sufficiently malicious :-)
Now it's my turn not to understand you. What is a "thing with identity"?
Basically I mean some packet of state, where is makes a difference which particular packet of state is referenced. So an IORef is a reference to a "thing with identity". Of course this is why the type of newIORef is.. newIORef :: a -> IO (IORef a) not simply.. newIORef :: a -> IORef a Any new "thing with identity" has be constructed via something like.. newThing :: IO Thing ..which makes it difficult to create such things at the top level, even though (as Simon M pointed out) there's no problem with actually using such things at the top level (assuming we had some magic that enabled their creation). Regards -- Adrian Hey
Adrian Hey <ahey@iee.org> writes:
The only real insanity with the current situation is the loss of referential transparency implied by the use of unsafePerformIO, which is why various pragma hacks and compiler switches need to be used (in order to prevent inappropriate substitutions). What I want to do is to make this or something similar "official", so the compiler really knows not to do this inappropriate subsitution in any event, even if the programmer forgot to use the necessary pragmas or compiler flags, or just didn't understand why they are needed.
It's not the only problem: v = unsafePerformIO (newIORef undefined) :: IORef a You can store an Integer there and try to take out a String. -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
On Tuesday 12 Oct 2004 10:47 pm, Marcin 'Qrczak' Kowalczyk wrote:
Adrian Hey <ahey@iee.org> writes:
The only real insanity with the current situation is the loss of referential transparency implied by the use of unsafePerformIO, which is why various pragma hacks and compiler switches need to be used (in order to prevent inappropriate substitutions). What I want to do is to make this or something similar "official", so the compiler really knows not to do this inappropriate subsitution in any event, even if the programmer forgot to use the necessary pragmas or compiler flags, or just didn't understand why they are needed.
It's not the only problem: v = unsafePerformIO (newIORef undefined) :: IORef a You can store an Integer there and try to take out a String.
Yes of course, this is a well known problem with unsafePerformIO. This is why in my earlier post I suggested that the use of special syntax would also be a cue for the compiler to impose typing restrictions. Regards -- Adrian Hey
I have put some thought, some time ago, into the 'global initializers' problem in haskell but for various reasons never wrote up my conclusions. The issues are 1) polymorphic references allow breaking of typesafety 2) when do the initializers get evaluated 3) do we need it? I will address these points in no particular order. 3) yes. the {-# noinline :: fooVar #-} fooVar = unsafePerformIO $ newIORef 0 is a very common idiom in real programs, and very difficult to work around not having. and if that is not enough, a couple more points * we can do it horribly inefficiently and unsafely already and the world has not collapsed: via getting and setting strings in the evironment we can create global variables holding read/showable values. via writing and reading temporary files and via the FFI just a foreign import "&global_var" :: Ptr Int note that we do not need any foregin code, just an object which allocates the space in the bss for global_var, the fact we can access and work with such space from haskell, but have no way to allocate it is quite telling that there is something missing in the language. 1) This is a real problem. A straightforward solution is to enforce top-level IO actions to be monomorphic. This is not a big restriction, as it is the exact same restriction placed on bindings in 'do' blocks or on lambda expressions. 2) is the tricky one. One proposed solution is to treat the global binding fooVar <- newIORef 0 as equivalant to fooVar = unsafePerformIO $ newIORef 0 with the appropriate compiler magic invoked to make sure that optimizations do not ruin the intended effect that newIORef 0 is executed exactly once and shared by (and only by) all uses of fooVar. This has the advantage of being very easy to implement with ghc as it currently is. ghc can just rewrite it as the above and turn off cse for the module and inlining for the binding. but there are strong disadvantages: * we can observe execution order by making the top level bindings have side effects. * We may need or want the optimizations we have to turn off, A haskell implementation with aggressive inter-module optimization might end up having to turn them off for the whole program. * A semantic mess to formalize and hence to optimize (IMHO) * not optimally efficient, a thunk for 'fooVar' is still created, and the IORef is created on the heap. A better way ============ The other way draws on the works done with fixIO and recursive monadic bindings and is much more preferable in my estimation. In addition, Like other syntatic sugar, it admits a simple rewriting to core haskell without any special optimizer/compiler magic required and most of the semantics work has already been done. The following papers have background material: Recursive Monadic Bindings: http://www.cse.ogi.edu/PacSoft/projects/rmb/mfix.ps.gz Semantics of fixIO: http://www.cse.ogi.edu/PacSoft/projects/rmb/fics.ps.gz Unlike some other rewritings, it is not recommended this actually be used to compile haskell programs (among other things, it would break separate compilation as stated), the rewriting is used to show the coorespondence between the semantics of fixIO as written in the paper and global bindings. An actual efficient way to implement this idea that behaves identically and allows separate compilation follows later. The basic idea is that your entire program behaves as if in a giant 'mdo' block, ordered in module dependency order. so, module Main import Bob fooVar <- newIORef bob main = readIORef fooVar >>= print module Bob bob <- return 3 is transformed into main = mdo bob <- return 3 let main' = readIORef fooVar >>= print fooVar <- newIORef bob main' (if we were to actually carry out this transformation, appropriate renamings will have to occur to avoid name capture, and the let bindings in mdo must be of the polymorphic variety (see 3.2 of the first paper) ) note that normal values must be pushed ahead of all the global bindings to ensure they scope properly over whery they may be used. (assuming the polymorphic mdo notation described in the paper) so the general tranformation results in newMain = mdo # let values for module Foo # global bindings for module Foo # let values for mutually recursive modules Bar and Baz # global bindings for modules Bar and Baz # let valuse for module Main # global bindings for module Main Main.main note that mutually recursive modules must be treated as a unit and hence the order their bindings occur in (across modules) is not well defined. we can chalk this up to mutually recursive module oddness in general and should not be a problem in practice. This has a lot of nice properties, * no unsafePerformIO like stuff. * evaluation order of the functional code does not matter, the IO actions are carried out in the order stated in the module at initiation. * well defined semantics as mentioned in the previously mentioned papers * admits a very efficient implementation How to efficiently implement this: module Foo where fooVar <- newIORef 0 showsVar <- newIORef 0 tick = modifyIORef fooVar (+1) showTicks = do putStr "Number of ticks: " readIORef fooVar >>= print modifyIORef showsVar (+1) So, the compilation procedes as statically allocate space for pointers to two haskell thunks. perhaps in the bss or initialized data segment. the two pointers shousd originally point to the equivalant of error "Strict Loop in global bindings" (or a better error message, perhaps integrating a line number/file) the module creates an internal procedure say 'Foo._init_' which upon first run, sets a flag saying it is being run, calls _init_ in each of its imported modules, executes each IO action assosiated with its global bindings in turn, and updates them to point to the value retured by the IO action. if the flag saying it has already (or is in the progress of) being run is already set, the _init_ procedure does nothing and returns immediatly. we then use this main' as our entry point. main' = do Main._init_ Main.main Note that all the error thunks (or holes) are completly removed by the time main starts. the only way to get one is to create recursive bindings that are strict in a loop. such as x <- return $! x which is just as well defined as note that x <- return x is equivalant to x = x as we expect. the global variables can be allocated statically, meaning we can generate much better code with offsets directly to our values. strictness analysis can be applied and if it determines an integral IORef is always passed strict values for instance, it can unbox the global int. for the purposes of GC, the global variables can be treated like CAFs. So, I believe this is a clean and efficient way to allow global state in haskell. sorry for the long post and bad grammar, I had to just sit and punch this out or I'd never write it down :) John -- John Meacham - ⑆repetae.net⑆john⑈
John Meacham wrote:
I have put some thought, some time ago, into the 'global initializers' problem in haskell but for various reasons never wrote up my conclusions.
I'm not really qualified to answer, but does anyone think that this paper might have a solution? http://www.eecs.harvard.edu/~ccshan/prepose/prepose.pdf "The configurations problem is to propagate run-time preferences throughout a program, allowing multiple concurrent configuration sets to coexist safely under statically guaranteed separation. This problem is common in all software systems, but particularly acute in Haskell, where currently the most popular solution relies on unsafe operations and compiler pragmas. We solve the configurations problem in Haskell using only stable and widely implemented language features like the type-class system. In our approach, a term expression can refer to run-time configuration parameters as if they were compile-time constants in global scope. Besides supporting such intuitive term notation and statically guaranteeing separation, our solution also helps improve the program's performance by transparently dispatching to specialized code at run-time. We can propagate any type of configuration data---numbers, strings, IO actions, polymorphic functions, closures, and abstract data types. No previous approach to propagating configurations implicitly in any language provides the same static separation guarantees." Greg Buchholz
On 13 Oct 2004, at 00:04, Greg Buchholz wrote:
John Meacham wrote:
I have put some thought, some time ago, into the 'global initializers' problem in haskell but for various reasons never wrote up my conclusions.
I'm not really qualified to answer, but does anyone think that this paper might have a solution?
Yes, that is a solution to an instance of the problem. The discussions here are looking for a different one... possibly a more general one. Jules
On 12 Oct 2004, at 23:33, John Meacham wrote:
and via the FFI just a foreign import "&global_var" :: Ptr Int note that we do not need any foregin code, just an object which allocates the space in the bss for global_var, the fact we can access and work with such space from haskell, but have no way to allocate it is quite telling that there is something missing in the language.
Yes, that's weird, isn't it?
The basic idea is that your entire program behaves as if in a giant 'mdo' block, ordered in module dependency order.
I wondered if something like that could work, but I wasn't sure that mdo allowed recursion in its let-bindings... Jules
On Wed, Oct 13, 2004 at 07:20:06AM +0100, Jules Bean wrote:
On 12 Oct 2004, at 23:33, John Meacham wrote:
and via the FFI just a foreign import "&global_var" :: Ptr Int note that we do not need any foregin code, just an object which allocates the space in the bss for global_var, the fact we can access and work with such space from haskell, but have no way to allocate it is quite telling that there is something missing in the language.
Yes, that's weird, isn't it?
Yeah, I noticed this oddity when I found I could create very fast global variables via this trick and peeks and pokes to read/write them. but the stub C file with just a 'int var;' was sort of silly, especially when I knew that didn't generate any code at all, just some allocated space.
The basic idea is that your entire program behaves as if in a giant 'mdo' block, ordered in module dependency order.
I wondered if something like that could work, but I wasn't sure that mdo allowed recursion in its let-bindings...
The mdo implementation in ghc does not actually, but that is mainly to simplify some odd cases when a variable will appear monomorphic in one place and polymorphic in another and is not actually a restriction of recursive monads in general nor does it change the semantics of the fixpoint operations on them, which is the main item I was trying to leverage from those papers. In section 3.2 of the mdo paper, an alternate translation of mdo is given which does allow polymorphic let bindings and that is the translation which should be used if you were to actually implement the program transformation I described. Note that since we 'push' all the normal declarations of a module above all monadic bindings, we don't run into the polymorphic problem described in the paper, as our polymorphic normal declarations scope over the entire expression in which they may be used. John -- John Meacham - ⑆repetae.net⑆john⑈
Nothing to add, other than to point people at a different solution, using implicit parameters and rank-2 types: http://www.cs.chalmers.se/~rjmh/Globals.ps Admittedly, John's approach interacts in a prickly manner with the monomorphism restriction, but it does allow for the safe updating of global variables (which the unsafePerformIO technique does not). Cheers, Andy -- Andy Moran Ph. (503) 626 6616, x113 Galois Connections Inc. Fax. (503) 350 0833 12725 SW Millikan Way, Suite #290 http://www.galois.com Beaverton, OR 97005 moran@galois.com
Let me add a few thoughts on the global variables problem and the proposed solutions. 1) I strongly disagree with ideas to execute IO actions implicitly in whatever defined or undefined sequence before or during main for whatever reasons. If initialization actions are necessary, they should always be performed explicitly from inside main. If modules or libraries need init actions to be performed, then such actions should be exported (and documented) in the normal way. The reasons are manyfold: a) Explicit initialization means that the end-programmer has complete control over when and if such actions get executed, which is a Good Thing. It may very well happen, for example, that a program needs to perform some init action on its own before doing the one for an imported library. Or that one doesn't want to execute the library's init action at all because what one wants to use from it doesn't need the initialization. b) Only explicitly called init actions may be parameterized. This means that any library that needs its initialization action to be parameterized by the user has to use the explicit variant anyway. c) Implicitly executed init actions make the code harder to reason about. d) Calling user code before main() was introduced in C++ (it is not possible in C). It took a while for programmers (myself included) to realize that the apparent elegance and convenience of this has a huge cost in maintainability, especially (but not only) in connection with shared libraries. AFAIK, using static objects with non-trivial constructors in libraries is nowadays deemed bad practice and rightly so. I know of one case where this has been the cause for inexplicable crashes when porting a library from one unix variant to another one. This delayed the release of the port for at least a year! e) It has already been noted that if init actions from other modules are to be executed implicity, then the compiler needs to determine which module init actions to perform. The straight forward 'solution' is to use the import lists. This would imply that changing the import list of a module has potentially far reaching side-effects. This could lead to very obscure bugs. 2) I agree that avoiding global variables is often inconvenient. Even if we combine all of them into a single compound value ('globals'), at least this one value has to be threaded through a lot of functions that aren't in the least interested in them. Aside from making the code fragile against changes, it introduces a certain amount of noise into the code, making it harder to read and understand. I disagree though with what On Wednesday 13 October 2004 00:33, John Meacham wrote:
The issues are [...] 3) do we need it? [...] 3) yes. the {-# noinline :: fooVar #-} fooVar = unsafePerformIO $ newIORef 0 is a very common idiom in real programs, and very difficult to work around not having.
It may be tedious and inconvenient to add a record of 'globals' as argument to all the functions involved, but difficult it is not. It is in fact so simple that it could be easily automated. What I originally wanted to propose was therefore some sort of source-to-source program transformation that adds all the intermediate extra function arguments. Then I realized that this is almost exactly what the so called 'implicit parameters' extension to Haskell is all about, and that using them as a replacement for global variables has already been proposed by John Hughes (http://www.cs.chalmers.se/~rjmh/Globals.ps). He notes in this paper that implicit parameters, as implemented in GHC, infect the types of all the involved functions with extra contexts. Although in principle this is exactly what we want, it implies that the addition of an implicit parameter (or changing its type) potentially invalidates a lot of function signatures (if these are given explicitly). This is unfortunate because it makes the code fragile and partly re-introduces the tedium we wanted to avoid in the first place. What I've been asking myself is: Wouldn't it be possible for the compiler to silenty add the implicit parameter type constraints behind the scenes? It already does so for functions without a signature, so why not do it for functions with an explicit signature, too? I realize that this would be a break with the Haskell tradition to *either* infer types *or* use the programmer given type signatures. Nevertheless, if this would work, we'd have a very clean *and* easily usable solution to the global variables problem. Ben P.S. I like the '?identifier' syntax for implicit parameters because it clearly marks such entities as dynamically bound instead of statically: you wouldn't even try to find the definition of such a thing in the surrounding scope.
Benjamin Franksen wrote: | 1) I strongly disagree with ideas to execute IO actions | implicitly in whatever defined or undefined sequence | before or during main for whatever reasons. I agree with the objections you make. Having full IO actions as initialization actions might be a bit too much. | What I originally wanted to propose was therefore some | sort of source-to-source program transformation that adds | all the intermediate extra function arguments. Then I | realized that this is almost exactly what the so called | 'implicit parameters' extension to Haskell is all about, | and that using them as a replacement for global variables | has already been proposed by John Hughes. The problem with John's approach is that it breaks modularity. It does this in two ways: (1) Whenever a module uses an implicit parameter like that, it has to have a name that is different from all implicit parameters used by any other (future) module. (Yes, implicit paramers cannot be quantified by a module name.) This is difficult to ensure. (2) Having the implicit parameter breaks the abstraction barrier. I might want to re-implement a module that does not make use of global variables, into one that uses a cache or hash-table or whatever (think BDD library), and not change the interface of the functions that are provided. | What I've been asking myself is: Wouldn't it be possible | for the compiler to silenty add the implicit parameter | type constraints behind the scenes? You would be back at square 1, since your program will still look and behave exactly the same as a program that implicitly executes all initializations; the only difference is implementation. I have a different proposal. Imagine a commutative monad, CIO. Commutative monads have the property that it does not matter in what order actions are performed, they will have the same effect. In other words, for all m1 :: CIO A, m2 :: CIO B, k :: A -> B -> CIO C, it should hold that: do a <- m1 do b <- m2 b <- m2 === a <- m1 k a b k a b Now, one could imagine an extension X of Haskell98, in which modules are allowed to contain definitions of the form: p <- m Here, p is a (monomorphic) pattern, and m is of type CIO A, for some type A. CIO is an (abstract) monad provided in a library module, just like IO is today. One could wonder where the primitive actions in the monad CIO come from? Well, library providers (compilers) could provide these. For example: newIORefCIO :: a -> CIO (IORef a) newEmptyMVarCIO :: CIO (MVar a) And so on. The implementer of these functions has to guarantee that the actions do not destroy the commutativity of the CIO monad. This is done in the same way as today, compiler writers and users of the FFI guarantee that certain primitive operations such as + on Ints are pure. The FFI could even adapt CIO as a possible result type (instead of having just pure functions or IO functions in the FFI). (One could even imagine a compiler feature that provides us with a function: unsafeIOtoCIO :: IO a -> CIO a But this could of course never be a part of the official Haskell98 extension X!) Comments welcome. Kind regards, /Koen
On Thursday 04 November 2004 16:16, Koen Claessen wrote:
The problem with John's approach is that it breaks modularity. It does this in two ways:
(1) Whenever a module uses an implicit parameter like that, it has to have a name that is different from all implicit parameters used by any other (future) module. (Yes, implicit paramers cannot be quantified by a module name.) This is difficult to ensure.
I haven't thought of this before. It is a drawback, indeed. Is there any convincing technical reason why implicit parameters cannot be "quantified by a module name" (whatever that may mean exactly).
(2) Having the implicit parameter breaks the abstraction barrier. I might want to re-implement a module that does not make use of global variables, into one that uses a cache or hash-table or whatever (think BDD library), and not change the interface of the functions that are provided.
| What I've been asking myself is: Wouldn't it be possible | for the compiler to silenty add the implicit parameter | type constraints behind the scenes?
You would be back at square 1, since your program will still look and behave exactly the same as a program that implicitly executes all initializations; the only difference is implementation.
No, not at all. "behind the scenes" refered only to adding appropriate *type annotations* (i.e. implicit parameter constraints). You would still need to perform all initialization explicitly inside main. If this were possible, at least your critique point 2 would no longer apply. Ben Rudiak-Gould explained very clearly what I meant.
I have a different proposal.
Imagine a commutative monad, CIO. Commutative monads have the property that it does not matter in what order actions are performed, they will have the same effect. In other words, for all m1 :: CIO A, m2 :: CIO B, k :: A -> B -> CIO C, it should hold that:
do a <- m1 do b <- m2 b <- m2 === a <- m1 k a b k a b
Now, one could imagine an extension X of Haskell98, in which modules are allowed to contain definitions of the form:
p <- m
Here, p is a (monomorphic) pattern, and m is of type CIO A, for some type A. CIO is an (abstract) monad provided in a library module, just like IO is today.
One could wonder where the primitive actions in the monad CIO come from? Well, library providers (compilers) could provide these. For example:
newIORefCIO :: a -> CIO (IORef a) newEmptyMVarCIO :: CIO (MVar a)
And so on.
The implementer of these functions has to guarantee that the actions do not destroy the commutativity of the CIO monad. This is done in the same way as today, compiler writers and users of the FFI guarantee that certain primitive operations such as + on Ints are pure.
The FFI could even adapt CIO as a possible result type (instead of having just pure functions or IO functions in the FFI).
This proposal is very elegant and beautiful. There is one caveat, however. Commutativity is a property of the Monad in itself. Inside the CIO monad everything commutes, but does that mean actions inside CIO always commute with all other actions in IO? Instead of just being a commutative sub monad of IO, CIO would need to be a sub-monad in IO that has the property that its actions commute with every IO action. Only then would the order of execution be irrelevant. CIO would surely contain actions to create MVars and IORefs. Are there other primitive IO actions that belong to (this) CIO? *** On a different note, I remember that Eiffel (an imperative OO language) lacks global variables, too. As a replacement Eiffel has so called 'once' routines. These are executed only once per program run -- later calls just return the memoized result from the first time. This smells a lot like the unsafePerformIO+{- NoInline-} aproach to me. Ben
Koen Claessen wrote:
Benjamin Franksen wrote:
| 1) I strongly disagree with ideas to execute IO actions | implicitly in whatever defined or undefined sequence | before or during main for whatever reasons.
I agree with the objections you make. Having full IO actions as initialization actions might be a bit too much.
And I agree with this too.
| What I originally wanted to propose was therefore some | sort of source-to-source program transformation that adds | all the intermediate extra function arguments. Then I | realized that this is almost exactly what the so called | 'implicit parameters' extension to Haskell is all about, | and that using them as a replacement for global variables | has already been proposed by John Hughes.
The problem with John's approach is that it breaks modularity. It does this in two ways:
(1) Whenever a module uses an implicit parameter like that, it has to have a name that is different from all implicit parameters used by any other (future) module. (Yes, implicit paramers cannot be quantified by a module name.) This is difficult to ensure.
This is one of the several ways in which the current implementation of implicit parameters is broken. Clearly they *should* belong to the module namespace, and if we modify the implementation so that they do, the problem you describe here goes away.
(2) Having the implicit parameter breaks the abstraction barrier. I might want to re-implement a module that does not make use of global variables, into one that uses a cache or hash-table or whatever (think BDD library), and not change the interface of the functions that are provided.
I'm not convinced this is a problem either. All you have to do is use a single parameter (?MyModule.globals :: MyModule.Globals), where MyModule.Globals is an abstract type, and you've hidden your implementation as completely as if you had used unexported global variables.
| What I've been asking myself is: Wouldn't it be possible | for the compiler to silenty add the implicit parameter | type constraints behind the scenes?
You would be back at square 1, since your program will still look and behave exactly the same as a program that implicitly executes all initializations; the only difference is implementation.
I don't think this is what he means. The original implicit-parameter paper suggested an extension of Haskell to support partial constraints in type signatures, e.g. pretty :: ... => Doc -> String with the unspecified constraint being filled in by the type inferencer (section 5.4). I think the OP is proposing the same thing, except without the ellipsis: i.e. we just write pretty :: Doc -> String and the compiler infers pretty :: (?width :: Int) => Doc -> String, or whatever. This actually sounds like a very good idea to me.
I have a different proposal.
Imagine a commutative monad, CIO. [...]
newIORefCIO :: a -> CIO (IORef a) newEmptyMVarCIO :: CIO (MVar a)
Adrian Hey proposed a "SafeIO" monad with similar properties to yours. I have the same objection to both of them: a whole new monad and a bunch of interconversion functions seems like overkill for such a minor new language feature. And I have the same counter-proposal: why not use (forall s. ST s)? It's not commutative, but I think it has all of the properties we need. In particular, it isolates initialization actions well enough that it doesn't matter what order they're run in, or even whether they're run at all. So importing a module doesn't have side effects, and init actions can be implemented easily using unsafePerformIO without affecting the semantics. Note that the ST monad does not require higher-order polymorphism -- only the runST function requires that. ST is still useful without runST, as this example demonstrates. -- Ben
On Thursday 04 November 2004 17:20, Ben Rudiak-Gould wrote:
Koen Claessen wrote:
(1) Whenever a module uses an implicit parameter like that, it has to have a name that is different from all implicit parameters used by any other (future) module. (Yes, implicit paramers cannot be quantified by a module name.) This is difficult to ensure.
This is one of the several ways in which the current implementation of implicit parameters is broken. Clearly they *should* belong to the module namespace, and if we modify the implementation so that they do, the problem you describe here goes away.
I have thought about this and I am not sure that this is possible or even makes sense. Remember that implicit parameters cannot be bound at the top level. They must be 'let' or 'where' bound. Indeed, when using them for a global variabe replacement, we do *not* want them at the top level, because we want to initialize them explicitly: do_stuff_with_x = do use_it ?x -- refers to the x bound in main main = do x <- initialize_x do_stuff_with_x What if do_stuff_with_x is in another module? We could say that x belongs to namespace Main (i.e. the module in which it is bound) do_stuff_with_x = do use_it ?Main.x but then do_stuff_with_x cannot be called from any other module. This is bad. We could also say that x belongs to the namespace where x is used, but this would lead to similar problems, i.e. all usages of an implicit parm would be limited to one and only one module. This is bad too. Ben (the other one)
Benjamin Franksen wrote:
On Thursday 04 November 2004 17:20, Ben Rudiak-Gould wrote:
This is one of the several ways in which the current implementation of implicit parameters is broken. Clearly they *should* belong to the module namespace, and if we modify the implementation so that they do, the problem you describe here goes away.
I have thought about this and I am not sure that this is possible or even makes sense. Remember that implicit parameters cannot be bound at the top level. They must be 'let' or 'where' bound.
Implicit parameter names (not bindings!) are public by their nature. This is another way in which they're different from ordinary local bindings. When you say "let x = ... in ...", there's no point putting x in the module namespace because it's restricted to the current expression by lexical scoping anyway. But with implicit parameters you run into merging problems if a parameter like ?globals is used in two different modules for different purposes, and both modules export a function with ?globals in its type. I don't think the issues here are any different that what we have to deal with anyway with user-defined datatypes and type classes showing up in exported types. If module Main imports module X, and X exports a function with an implicit parameter ?p in its type, then ?p has to be in X's namespace, or in the namespace of a module imported by X. Otherwise X can't refer to it at all. It can't be in the Main namespace unless X imports Main circularly. Export, import, and mention of implicit parameters follow the same rules as other module-qualified identifiers. But I just realized that it will probably be necessary to declare (not bind!) implicit parameters at the top level to avoid capture problems. (E.g. module X uses a parameter called ?p, but one day module Y, imported unqualified by X, decides to add its own parameter ?p, and suddenly X's ?p refers to ?Y.p instead of ?X.p.) This is unfortunate, because it probably means adding a new keyword to the language. -- Ben
Ben Rudiak-Gould wrote: | I'm not convinced this is a problem either. All you have | to do is use a single parameter (?MyModule.globals :: | MyModule.Globals), where MyModule.Globals is an abstract | type, and you've hidden your implementation as completely | as if you had used unexported global variables. Are you suggesting to always add the context (?MyModule.globals :: MyModule.Globals) to every function in every module you implement? (My example concerned a module that was previously implemented without global variables, and now was going to be implemented with global variables.) | [...] The original implicit-parameter paper suggested an | extension of Haskell to support partial constraints in | type signatures, e.g. | | pretty :: ... => Doc -> String | | with the unspecified constraint being filled in by the | type inferencer (section 5.4). (Ah! I had forgotten about that. See also: http://www.mail-archive.com/haskell@haskell.org/msg05186.html :-) | I think the OP is proposing the same thing, except | without the ellipsis: i.e. we just write | | pretty :: Doc -> String | | and the compiler infers pretty :: (?width :: Int) => Doc | -> String, or whatever. This actually sounds like a very | good idea to me. I think hiding the fact that certain objects are not constants but functions is a bad idea, because it will break sharing in a lazy implementation. | Adrian Hey proposed a "SafeIO" monad with similar | properties to yours. I have the same objection to both of | them: a whole new monad and a bunch of interconversion | functions seems like overkill for such a minor new | language feature. I was not aware of his proposal. I don't think it is that bad: * 1 new monad * for each current safe IO operation, 1 new operation (read: newIORef. What else?) * possibly, a function convertCIOtoIO :: CIO a -> IO a (* part of compilers: a function unsafeIOtoCIO :: IO a -> CIO a) That's it! | And I have the same counter-proposal: why not use (forall | s. ST s)? It's not commutative, but I think it has all of | the properties we need. Interesting idea. However, when I then provide a function for creating an IORef (which is what this extension would be used for mostly), I get this: newIORefST :: a -> ST s (IORef a) Which is probably not what you want. | So importing a module doesn't have side effects, and init | actions can be implemented easily using unsafePerformIO | without affecting the semantics. I don't understand this remark. | Note that the ST monad does not require higher-order | polymorphism -- only the runST function requires that. ST | is still useful without runST, as this example | demonstrates. So, if I get it right, you want to use (forall s . ST s) because it avoids adding yet another monad to Haskell? Regards, /Koen
On Thursday 04 November 2004 18:28, Koen Claessen wrote:
Ben Rudiak-Gould wrote: | I think the OP is proposing the same thing, except | without the ellipsis: i.e. we just write | | pretty :: Doc -> String | | and the compiler infers pretty :: (?width :: Int) => Doc | -> String, or whatever. This actually sounds like a very | good idea to me.
I think hiding the fact that certain objects are not constants but functions is a bad idea, because it will break sharing in a lazy implementation.
You probably mean the case where the implicit parameter is the only one. I don't see why that would "break sharing in a lazy implementation". The compiler is fully aware of the complete type of all functions and can use sharing whenever appropriate. Ben
Benjamin Franksen wrote: | > I think hiding the fact that certain objects are not | > constants but functions is a bad idea, because it will break | > sharing in a lazy implementation. | | You probably mean the case where the implicit parameter | is the only one. I don't see why that would "break | sharing in a lazy implementation". The compiler is fully | aware of the complete type of all functions and can use | sharing whenever appropriate. Maybe you misunderstood me. What about: x :: Int x = expensive ?foo The type of x makes it look like a value that can be shared. But really it cannot since it depends on ?foo. How can a compiler share different uses of x? /Koen
Koen Claessen wrote:
Ben Rudiak-Gould wrote:
| I'm not convinced this is a problem either. All you have | to do is use a single parameter (?MyModule.globals :: | MyModule.Globals), where MyModule.Globals is an abstract | type, and you've hidden your implementation as completely | as if you had used unexported global variables.
Are you suggesting to always add the context (?MyModule.globals :: MyModule.Globals) to every function in every module you implement? (My example concerned a module that was previously implemented without global variables, and now was going to be implemented with global variables.)
Okay, I see. The implicit parameter approach gives you more flexibility than the global variable approach, since you can create and use more than one set of "globals", and supply arguments to the factory function. If you need that flexibility, obviously you can't avoid changing the public interface. If you don't need that flexibility, I think real global variables are fine. I have my own pet proposal for those, after all. :-)
I think hiding the fact that certain objects are not constants but functions is a bad idea, because it will break sharing in a lazy implementation.
Okay, this is a problem. We'd have to tweak the monomorphism restriction a bit.
| Adrian Hey proposed a "SafeIO" monad with similar | properties to yours. I have the same objection to both of | them: a whole new monad and a bunch of interconversion | functions seems like overkill for such a minor new | language feature.
I was not aware of his proposal. I don't think it is that bad:
* 1 new monad
* for each current safe IO operation, 1 new operation (read: newIORef. What else?)
At least newMVar, newEmptyMVar, newArray, newArray_, and newListArray. I'm not sure how you'd handle the last three, since they're overloaded and I don't think that all of the instances of MArray are safe to create in CIO.
| And I have the same counter-proposal: why not use (forall | s. ST s)? It's not commutative, but I think it has all of | the properties we need.
Interesting idea. However, when I then provide a function for creating an IORef (which is what this extension would be used for mostly), I get this:
newIORefST :: a -> ST s (IORef a)
Which is probably not what you want.
This is solved by merging the IO and ST monads, something that ought to be done anyway: type IO = ST RealWorld type IORef a = Ref RealWorld a type STRef s a = Ref s a newRef :: a -> ST s (Ref s a) -- replaces newIORef and newSTRef readRef :: Ref s a -> ST s a writeRef :: Ref s a -> a -> ST s () ... A top-level init action would look like r <- newRef 'x' The RHS has type (forall s. ST s (Ref s Char)). The runtime system runs it through (id :: forall a. (forall s. ST s a) -> ST RealWorld a), with a resulting type of ST RealWorld (Ref RealWorld Char), which is the same as IO (IORef Char). So r ends up with the type IORef Char. The same newRef function works in ST monad and IO monad computations. You don't have to decide ahead of time whether you want the versatility of ST or the convenience of IO. The compiler will automatically infer a type of IO x for any function which actually does I/O, and (forall s. ST s x) for a function which just mucks around with Refs and MArrays. This is one small step towards getting rid of the current status of IO as a dumping ground for everything that might need to be used alongside genuine I/O. I don't think this even breaks existing code -- though I'm prepared to be presented with counterexamples. A slight wart is that we have to move MVars into ST as well if we want to create them in init actions. This doesn't break anything, but it's a bit silly because they're basically useless outside IO.
| So importing a module doesn't have side effects, and init | actions can be implemented easily using unsafePerformIO | without affecting the semantics.
I don't understand this remark.
This isn't specific to my proposal. I just meant that if we allow unrestricted IO actions then we have to worry about which ones get run and when they get run. If we run all actions at the beginning, then importing a module into your program has side effects (versus not mentioning it at all). On the other hand if those actions are appropriately restricted, then the program can't tell whether they've been run or not, and so importing a module doesn't have side effects, and also we don't have to worry about the (difficult, inefficient) engineering problem of making all the actions run before main; we can run them on demand, as though they were individually wrapped in unsafePerformIO.
| Note that the ST monad does not require higher-order | polymorphism -- only the runST function requires that. ST | is still useful without runST, as this example | demonstrates.
So, if I get it right, you want to use (forall s . ST s) because it avoids adding yet another monad to Haskell?
Better than that, it reduces the number of monads in Haskell. :-) John Peterson's post intrigues me, though: maybe there is good reason to add a CIO monad if we get other benefits from it as well. But I don't (yet) understand what those benefits are. I'd like to see an example of what CIO can do that (forall s. ST s) can't. (There are definitely things that ST can do that CIO can't -- write values into those mutable arrays before returning them, for example.) -- Ben
I don't quite understand this thread - There are already the equivalent of IOrefs in the ST monad called STrefs. You can do newSTRef etc... you can use stToIO to embed an ST operation in the IO monad and this is safe. You can also use the unsafe ioToST, provided you are careful. To me adding stateful global variables is a bad thing and these are some reasons that spring to mind: Why do want global variables? They are like goto's the source of many programming errors... global constants maybe. To me global variables seems like a step backwards to languages like visual basic. One of the advantages of a functional language is that a function only depends on it's arguments, not some 'hidden' state which makes debugging hard. As for top level init functions - there already is one, its called 'main'. If you want to initialise some state, call it from main, and return the state from the initialisation function. Finally if something does IO (that is communicates with something stateful) that is not a state thread (due to the documented properties of the ST monad) it should be in the IO monad - thats what its there for. Keean. Ben Rudiak-Gould wrote:
Koen Claessen wrote:
Ben Rudiak-Gould wrote:
| I'm not convinced this is a problem either. All you have | to do is use a single parameter (?MyModule.globals :: | MyModule.Globals), where MyModule.Globals is an abstract | type, and you've hidden your implementation as completely | as if you had used unexported global variables.
Are you suggesting to always add the context (?MyModule.globals :: MyModule.Globals) to every function in every module you implement? (My example concerned a module that was previously implemented without global variables, and now was going to be implemented with global variables.)
Okay, I see. The implicit parameter approach gives you more flexibility than the global variable approach, since you can create and use more than one set of "globals", and supply arguments to the factory function. If you need that flexibility, obviously you can't avoid changing the public interface. If you don't need that flexibility, I think real global variables are fine. I have my own pet proposal for those, after all. :-)
I think hiding the fact that certain objects are not constants but functions is a bad idea, because it will break sharing in a lazy implementation.
Okay, this is a problem. We'd have to tweak the monomorphism restriction a bit.
| Adrian Hey proposed a "SafeIO" monad with similar | properties to yours. I have the same objection to both of | them: a whole new monad and a bunch of interconversion | functions seems like overkill for such a minor new | language feature.
I was not aware of his proposal. I don't think it is that bad:
* 1 new monad
* for each current safe IO operation, 1 new operation (read: newIORef. What else?)
At least newMVar, newEmptyMVar, newArray, newArray_, and newListArray. I'm not sure how you'd handle the last three, since they're overloaded and I don't think that all of the instances of MArray are safe to create in CIO.
| And I have the same counter-proposal: why not use (forall | s. ST s)? It's not commutative, but I think it has all of | the properties we need.
Interesting idea. However, when I then provide a function for creating an IORef (which is what this extension would be used for mostly), I get this:
newIORefST :: a -> ST s (IORef a)
Which is probably not what you want.
This is solved by merging the IO and ST monads, something that ought to be done anyway:
type IO = ST RealWorld type IORef a = Ref RealWorld a type STRef s a = Ref s a
newRef :: a -> ST s (Ref s a) -- replaces newIORef and newSTRef readRef :: Ref s a -> ST s a writeRef :: Ref s a -> a -> ST s () ...
A top-level init action would look like
r <- newRef 'x'
The RHS has type (forall s. ST s (Ref s Char)). The runtime system runs it through (id :: forall a. (forall s. ST s a) -> ST RealWorld a), with a resulting type of ST RealWorld (Ref RealWorld Char), which is the same as IO (IORef Char). So r ends up with the type IORef Char.
The same newRef function works in ST monad and IO monad computations. You don't have to decide ahead of time whether you want the versatility of ST or the convenience of IO. The compiler will automatically infer a type of IO x for any function which actually does I/O, and (forall s. ST s x) for a function which just mucks around with Refs and MArrays. This is one small step towards getting rid of the current status of IO as a dumping ground for everything that might need to be used alongside genuine I/O.
I don't think this even breaks existing code -- though I'm prepared to be presented with counterexamples.
A slight wart is that we have to move MVars into ST as well if we want to create them in init actions. This doesn't break anything, but it's a bit silly because they're basically useless outside IO.
| So importing a module doesn't have side effects, and init | actions can be implemented easily using unsafePerformIO | without affecting the semantics.
I don't understand this remark.
This isn't specific to my proposal. I just meant that if we allow unrestricted IO actions then we have to worry about which ones get run and when they get run. If we run all actions at the beginning, then importing a module into your program has side effects (versus not mentioning it at all). On the other hand if those actions are appropriately restricted, then the program can't tell whether they've been run or not, and so importing a module doesn't have side effects, and also we don't have to worry about the (difficult, inefficient) engineering problem of making all the actions run before main; we can run them on demand, as though they were individually wrapped in unsafePerformIO.
| Note that the ST monad does not require higher-order | polymorphism -- only the runST function requires that. ST | is still useful without runST, as this example | demonstrates.
So, if I get it right, you want to use (forall s . ST s) because it avoids adding yet another monad to Haskell?
Better than that, it reduces the number of monads in Haskell. :-)
John Peterson's post intrigues me, though: maybe there is good reason to add a CIO monad if we get other benefits from it as well. But I don't (yet) understand what those benefits are. I'd like to see an example of what CIO can do that (forall s. ST s) can't. (There are definitely things that ST can do that CIO can't -- write values into those mutable arrays before returning them, for example.)
-- Ben
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Keean Schupke <k.schupke@imperial.ac.uk> writes:
Why do want global variables?
Because they are more convenient than passing a state by hand. They increase modularity by avoiding putting the fact that a computation uses some global state in its type. You don't want stdin/stdout/stderr? Yes, *usually* it's a bad idea, but you are too idealistic.
One of the advantages of a functional language is that a function only depends on it's arguments, not some 'hidden' state which makes debugging hard.
We are primarily talking about state used in IO actions. They already depend e.g. on the state of the file system and the state of other computers communicating via a network. Letting them depend on values of a few global variables is not worse.
Finally if something does IO (that is communicates with something stateful) that is not a state thread (due to the documented properties of the ST monad) it should be in the IO monad - thats what its there for.
The point is to avoid threading global state to IO actions manually. Programming langages exist in order to conveniently write programs in, not only to admire their beauty. -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
On Friday 05 Nov 2004 1:23 pm, Marcin 'Qrczak' Kowalczyk wrote:
Keean Schupke <k.schupke@imperial.ac.uk> writes:
Why do want global variables?
Because they are more convenient than passing a state by hand. They increase modularity by avoiding putting the fact that a computation uses some global state in its type.
You don't want stdin/stdout/stderr?
IMHO this thread is somewhat misnamed anyway, for 2 reasons 1- We're not talking about simple mutable variables, we're talking about the safe construction of arbitrary "things with identity" without breaking referential transparency by use of unsafePerformIO hack. 2- These things are "top level", but almost certainly aren't "global" in most cases. As Marcin has pointed out, we happily make use of such things anyway, as provided by existing libraries. So the fact that these things exist (and can be used quite safely) but the language provides no way for the programmer to safely create them seems a bit strange. Regards -- Adrian Hey
--- Ben Rudiak-Gould <Benjamin.Rudiak-Gould@cl.cam.ac.uk> wrote:
This is solved by merging the IO and ST monads, something that ought to be done anyway:
type IO = ST RealWorld type IORef a = Ref RealWorld a type STRef s a = Ref s a
newRef :: a -> ST s (Ref s a) -- replaces newIORef and newSTRef readRef :: Ref s a -> ST s a writeRef :: Ref s a -> a -> ST s () ...
A top-level init action would look like
r <- newRef 'x'
The RHS has type (forall s. ST s (Ref s Char)). The runtime system runs it through (id :: forall a. (forall s. ST s a) -> ST RealWorld a), with a resulting type of ST RealWorld (Ref RealWorld Char), which is the same as IO (IORef Char). So r ends up with the type IORef Char.
The type of the expression: id (newRef 'x') isn't (ST RealWorld (Ref RealWorld Char)). GHC gives an error: Inferred type is less polymorphic than expected Quantified type variable `s' escapes In the first argument of `cast', namely `(newSTRef 'x')' I like this proposal. It merges not only IORef with STRef but also IOArray with STArray. In the base package there are also Data.HashTable and Data.Unique. Currently they can be used only from IO monad but it is perfectly safe to use them from ST if we change the interface a litle bit. data Hashtable s key val = .. data Unique s = .. The proposal gives us more generalizations and don't introduce any new monads. In order to get proper typing we must move the state parameter to the end: data Ref val s = .. data Hashtable key val s = .. data Unique s = .. In the above example the expression was: r <- newRef 'x' We need an additional function: runSTInit :: (forall s . ST s (a s)) -> a RealWorld In this case the compiler must translate the above to: r = runSTInit (newRef 'x') where the type of r is: r :: Ref Char RealWorld {- == IORef Char -} Note that 2-rank type of runSTInit doesn't allow to execute regular IO actions. Even that (ST s a) allows actions like readRef and writeRef. This allows to initialise local references but doesn't allow to access other toplevel reverences since they are bound to RealWorld state. Cheers, Krasimir __________________________________ Do you Yahoo!? Check out the new Yahoo! Front Page. www.yahoo.com
Krasimir Angelov wrote:
Note that 2-rank type of runSTInit doesn't allow to execute regular IO actions. Even that (ST s a) allows actions like readRef and writeRef. This allows to initialise local references but doesn't allow to access other toplevel reverences since they are bound to RealWorld state.
The proposal for merging ST and IO for refernce creation seems sound. I would like to point out that this does not address Adrian's problem as his example was that of initialising hardware which would require real IO actions. Also the gain from this seems small as I can already write: ref = newSTRef 0 and then I can use this safely from in the ST monad or the IO monad using "stToIO" - so using STRefs instead of IORefs would be as flexible as the proposal. Keean.
Note that 2-rank type of runSTInit doesn't allow to execute regular IO actions. Even that (ST s a) allows actions like readRef and writeRef. This allows to initialise local references but doesn't allow to access other toplevel reverences since they are bound to RealWorld state.
Thinking about this a bit more - isnt the real problem that the IO monad should infact be a monad-transformer layered ontop of the ST monad. That way ST actions could automatically be lifted to the IO monad using the normal mechanisms provided with monad-transformers. Keean.
--- Keean Schupke <k.schupke@imperial.ac.uk> wrote:
Note that 2-rank type of runSTInit doesn't allow to execute regular IO actions. Even that (ST s a) allows actions like readRef and writeRef. This allows to initialise local references but doesn't allow to access other toplevel reverences since they are bound to RealWorld state.
Thinking about this a bit more - isnt the real problem that the IO monad should infact be a monad-transformer layered ontop of the ST monad. That way ST actions could automatically be lifted to the IO monad using the normal mechanisms provided with monad-transformers.
Keean.
IO is already layered on top of ST and the stToIO is the lifting function. What does 'automatically be lifted' mean? Krasimir __________________________________ Do you Yahoo!? Check out the new Yahoo! Front Page. www.yahoo.com
Krasimir Angelov wrote:
ered on top of ST and the stToIO is the lifting function. What does 'automatically be lifted' mean?
Krasimir
For example with the state monad you can define: instance (MonadState st m,MonadT t m) => MonadState st (t m) where update = up . update setState = up . setState getState = up $ getState This makes any monad transformer applied to the StateMonad transformer an instance of the StateMonadTransformer. When you use getState, you do not have to prefix the lifting, the type checker unwinds the instance, and for each transformer it removes adds a lift. If is only possible to define the above for some monad-transformers. In other cases the lifts must be specific to the monad-transformer being lifted through, in which case you would define: instance Monad m => MonadTransX (MonadState m) where ... This would be for lifting functions of MonadTransX through MonadState specifically. Keean.
As I know the ST monad doesn't provide getState/setState functions. In order to get this kind of overloading we need to put all functions that deal with references in type class: class MonadRef m r where readRef :: r a -> m a writeRef :: a -> r a -> m () I guess that this is an overkill since we can just define IO as type IO a = ST RealWorld a Krasimir --- Keean Schupke <k.schupke@imperial.ac.uk> wrote:
Krasimir Angelov wrote:
ered on top of ST and the stToIO is the lifting function. What does 'automatically be lifted' mean?
Krasimir
For example with the state monad you can define:
instance (MonadState st m,MonadT t m) => MonadState st (t m) where update = up . update setState = up . setState getState = up $ getState
This makes any monad transformer applied to the StateMonad transformer an instance of the StateMonadTransformer. When you use getState, you do not have to prefix the lifting, the type checker unwinds the instance, and for each transformer it removes adds a lift.
If is only possible to define the above for some monad-transformers. In other cases the lifts must be specific to the monad-transformer being lifted through, in which case you would define:
instance Monad m => MonadTransX (MonadState m) where ...
This would be for lifting functions of MonadTransX through MonadState specifically.
Keean.
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
__________________________________ Do you Yahoo!? Check out the new Yahoo! Front Page. www.yahoo.com
Krasimir Angelov <ka2_mail@yahoo.com> writes:
I guess that this is an overkill since we can just define IO as
type IO a = ST RealWorld a
'instance MonadIO IO' would start to need some type system extensions. -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
On Monday 08 Nov 2004 10:52 am, Keean Schupke wrote:
Krasimir Angelov wrote:
Note that 2-rank type of runSTInit doesn't allow to execute regular IO actions. Even that (ST s a) allows actions like readRef and writeRef. This allows to initialise local references but doesn't allow to access other toplevel reverences since they are bound to RealWorld state.
The proposal for merging ST and IO for refernce creation seems sound. I would like to point out that this does not address Adrian's problem as his example was that of initialising hardware which would require real IO actions.
Not hardware specifically, but this is beside the point. I would like to point out that the only "IO" operation required by oneShot is the creation of an MVar, something that is perfectly OK with the proposed SafeIO/CIO restricted monad solutions (not sure about this one though). Regards -- Adrian Hey
I've been meaning to get into this debate ... Koen proposes: Imagine a commutative monad, CIO. Commutative monads have the property that it does not matter in what order actions are performed, they will have the same effect. In other words, for all m1 :: CIO A, m2 :: CIO B, k :: A -> B -> CIO C, it should hold that: do a <- m1 do b <- m2 b <- m2 === a <- m1 k a b k a b Now, one could imagine an extension X of Haskell98, in which modules are allowed to contain definitions of the form: p <- m Here, p is a (monomorphic) pattern, and m is of type CIO A, for some type A. CIO is an (abstract) monad provided in a library module, just like IO is today. One could wonder where the primitive actions in the monad CIO come from? Well, library providers (compilers) could provide these. For example: newIORefCIO :: a -> CIO (IORef a) newEmptyMVarCIO :: CIO (MVar a) And so on. The implementer of these functions has to guarantee that the actions do not destroy the commutativity of the CIO monad. This is done in the same way as today, compiler writers and users of the FFI guarantee that certain primitive operations such as + on Ints are pure. The FFI could even adapt CIO as a possible result type (instead of having just pure functions or IO functions in the FFI). This is definitely a step in the right direction. I am using this syntax already in Pan# and it's definitely the right thing to do. I would be hesitant to have any direct connection between this syntax and a specific monad (like CIO) - I want the same syntax in inner let statements and would like a context such as CMonad => to pop out when I see <- in a let, where CMonad is any commutative monad. The monads I use are a name supply and writer (which writes into a set rather than a list to preserve commutativity). As long as "CIO" had these I could use it but it would ne more interesting to avoid placing the initializations in a specific monad. Ultimately, I want the main program to see the initializing action: main :: CMonad m => m () -> IO () or if you have a specific monad in mind: main :: CIO () -> IO () The use of the CMonad type class is probably tough since you might want to blend modules with different initialization monads. You can address this with yet more type classes or just give up and use a single CIO type. But the main thing I want is a feedback path from the initializers to the main program. You could hack this by allowing CIO computations to get at a name supply and write into some kind of output for the main program but that seems a bit hacky to me. Much better to allow the main program to "run" the initialization code. All of this rambling non-withstanding, the idea of top level <- and a commutative monad are the really import ones. I think this would be an excellent way to address this issue in the real spirit of functional programming rather than just hacking things on and hoping they don't have any unintended consequences. John
On 2004-11-04 at 16:16+0100 Koen Claessen wrote:
Benjamin Franksen wrote:
| 1) I strongly disagree with ideas to execute IO actions | implicitly in whatever defined or undefined sequence | before or during main for whatever reasons.
I agree with the objections you make. Having full IO actions as initialization actions might be a bit too much.
I agree with that too. Let me propose what I think may be a simpler (though at present the dough is barely risen, let alone baked) alternative: external "constant" modules. First, I'd like to point out that having initialisation data that changes between runs of a programme is already possible: a programme could do IO to its own loadmodule. I mention this not as a serious suggestion, but to forestall objections about changing the content of a "constant" module. The idea is simply that we should provide a mechanism of saying to a compiler "this file (of data) is a module that exports only the variable v". Given the way IO and filesystems stand at the moment, this probably means that we'd have to restrict v to be a String (or [Word8]?), but the correct solution to that would be typed IO, and it's too early to talk about that. So we tell the compilation system that file /somewhere/contains-v contains the value of the variable v::String, and that it lives in the imaginary module Home.Of.V, and when the resulting programme is loaded, so is the content of /somewhere/contains-v, and v is bound to it. OS permitting the loading of the file should only be virtual. Now IO to /somewhere/contains-v should have no effect on the value of v, but next time the programme starts it can use the new value. Jón -- Jón Fairbairn Jon.Fairbairn@cl.cam.ac.uk
Jo'n Fairbairn wrote:
The idea is simply that we should provide a mechanism of saying to a compiler "this file (of data) is a module that exports only the variable v". ... So we tell the compilation system that file /somewhere/contains-v contains the value of the variable v::String, and that it lives in the imaginary module Home.Of.V, and when the resulting programme is loaded, so is the content of /somewhere/contains-v, and v is bound to it.
That seems to be similar to the following: http://www.haskell.org/pipermail/haskell-cafe/2002-September/003423.html In other words, we acknowledge the explicit phase separation and do the initialization phase explicitly, and in Haskell. The advantage is that the initialized variables look *exactly* as normal top-level variables (and may be polymorphic). I believe Haskell plug-ins (reported at Haskell Workshop 2004) is a far more advanced development and a practical realization of the similar idea. Incidentally, the above approach along with _many_ other approaches to global variables are surveyed at http://www.eecs.harvard.edu/~ccshan/prepose/prepose.pdf
Koen Claessen wrote:
Imagine a commutative monad, CIO. Commutative monads have the property that it does not matter in what order actions are performed, they will have the same effect. In other words, for all m1 :: CIO A, m2 :: CIO B, k :: A -> B -> CIO C, it should hold that:
do a <- m1 do b <- m2 b <- m2 === a <- m1 k a b k a b
... provided 'a' and 'b' are distinct variables, right? The following is legal in Haskell
do x <- ... let foo = ... x ... x <- ... let foo = ... x ...
That shows that variables add a bit more complexity to the question. Not only the dependency in actions should be considered, but also data dependency. In the above code, if action 'm2' happens to refer to the variable 'a', does commutativity still hold? Here are a few more similar example, assuming the proposed 'global level <-' syntax and the commutative nature of the action of creating an IORef:
z <- newIORef x x = [y1,y2] y1 <- newIORef t y2 <- newIORef t t <- newIORef True
Should this be accepted? Should the actions be executed as written? What if it were written
y1 <- (return $! t) >>= newIORef
A similar example:
class C a where op :: a -> a y <- newIORef (op (undefined::IORef Bool)) x <- newIORef True instance C (IORef Bool) where op _ = x
which shows that the data dependency analysis is a bit trickier than expected, even if the definitions appear to be `well-ordered'. Template Haskell may bring some new complications. Suppose module A imports module B and uses some of the functions of B at TH time. Module A is also free to use bindings of B at run time. Now, if module B has initializing actions, would they be executed once or twice? Would created CIORefs be shared across the phases? Would that behavior be consistent with respect to module A being compiled or interpreted? These questions do arise in Scheme -- where the agreement seems to be to have two kinds of imports: regular import and import for-syntax. [regarding partial signatures]
Ah! I had forgotten about that. See also:
http://www.mail-archive.com/haskell@haskell.org/msg05186.html
Incidently, some of that is already available in Haskell, http://pobox.com/~oleg/ftp/Haskell/types.html#partial-sigs
"Simon Marlow" <simonmar@microsoft.com> writes:
I'd like to add that while the implementation might be a little unsafe, there's no problem in principle with the semantics of top-level IORefs. We could add such a thing as a GHC extension, but it would be nice if it were an instance of a more general-purpose extension.
Top-level MVars also make sense, the extension should not be limited to IORefs. But permitting arbitrary values makes unsafePerformIO official and opens a hole in the type system for polymorphic variables (can such polymorphism be hidden in an apparently monomorphic datatype?). -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
participants (18)
-
Adrian Hey -
Andy Moran -
Ben Rudiak-Gould -
Benjamin Franksen -
Glynn Clements -
Greg Buchholz -
John Meacham -
John Peterson -
Jon Fairbairn -
Jules Bean -
Keean Schupke -
Keith Wansbrough -
Koen Claessen -
Krasimir Angelov -
Marcin 'Qrczak' Kowalczyk -
oleg@pobox.com -
Simon Marlow -
Vincenzo Ciancia