Graham Klyne wrote (snipped):
I like the principle of parameterizing Show to allow for different encoding environments (indeed, I had wondered as I was writing my earlier message if the two cases were really sufficient). Indeed, in the application area that interests me (Semantic Web) it could be beneficial to have different encoding options corresponding to different serializations of RDF (e.g. RDF/XML and Notation3).
I like the idea too, not just for Show but for any instances. It seems to me that in general you should be able to combine the convenience of the Haskell type system with the power of Standard ML's structures and functors. Something along these lines was done by Kahl & Scheffczyk ("Named Instances for Haskell Type Classes", Haskell Workshop 2001). In general I suspect you can do this without even having to extend the existing Haskell typesystem very far. The following language extensions seem sufficient. (1) for a class declaration, a way of declaring that a certain type represents a dictionary of functions for that class, for example class Show a (ShowDict,appFn) where ... would define the new class "Show" but also the new type (ShowDict a) representing dictionaries for that class. It also declares a value appFn which I shall explain in a moment. (2) for an instance declaration, a way of "naming" the corresponding dictionary. instance Show Int (intShowDict) where would create a value "intShowDict :: ShowDict Int" (3) a way of using the dictionary. For this we need (appFn), declaraed by the type declaration. appFn has the unorthodox type appFn :: ShowDict a -> (forall a . Show a => b) -> b
Do you need a language extension at all? You can certainly do it with the existing extensions! data ShowDict a instance Show (ShowDict a) where showsPrec _ (ShowDict a) = ... Keean George Russell wrote:
Graham Klyne wrote (snipped):
I like the principle of parameterizing Show to allow for different encoding environments (indeed, I had wondered as I was writing my earlier message if the two cases were really sufficient). Indeed, in the application area that interests me (Semantic Web) it could be beneficial to have different encoding options corresponding to different serializations of RDF (e.g. RDF/XML and Notation3).
I like the idea too, not just for Show but for any instances. It seems to me that in general you should be able to combine the convenience of the Haskell type system with the power of Standard ML's structures and functors. Something along these lines was done by Kahl & Scheffczyk ("Named Instances for Haskell Type Classes", Haskell Workshop 2001).
In general I suspect you can do this without even having to extend the existing Haskell typesystem very far. The following language extensions seem sufficient. (1) for a class declaration, a way of declaring that a certain type represents a dictionary of functions for that class, for example
class Show a (ShowDict,appFn) where ...
would define the new class "Show" but also the new type (ShowDict a) representing dictionaries for that class. It also declares a value appFn which I shall explain in a moment.
(2) for an instance declaration, a way of "naming" the corresponding dictionary.
instance Show Int (intShowDict) where
would create a value "intShowDict :: ShowDict Int"
(3) a way of using the dictionary. For this we need (appFn), declaraed by the type declaration. appFn has the unorthodox type
appFn :: ShowDict a -> (forall a . Show a => b) -> b
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Keean Schupke wrote:
Do you need a language extension at all? You can certainly do it with the existing extensions!
data ShowDict a instance Show (ShowDict a) where showsPrec _ (ShowDict a) = ...
I don't understand. How does that help you to, for example, use a function which requires Show Int but (say) substitute the standard function for which which shows in hexadecimal?
Easy: data ShowHex a instance Show (ShowHex a) where showsPrec _ (ShowHex a) = showHex a main = putStrLn $ (show (ShowHex 27)) Here, with labelled instances you would write: show ShowHex 27 instead you write: show (ShowHex 27) Keean. George Russell wrote:
Keean Schupke wrote:
Do you need a language extension at all? You can certainly do it with the existing extensions!
data ShowDict a instance Show (ShowDict a) where showsPrec _ (ShowDict a) = ...
I don't understand. How does that help you to, for example, use a function which requires Show Int but (say) substitute the standard function for which which shows in hexadecimal?
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Of course if you want to do it to code independantly of type you need to redifine show: data ShowHex = ShowHex class ShowDict t a where showDict :: a -> ShowS instance ShowDict ShowHex Int where showDict a = showHex a test :: ShowDict t a => t -> a -> ShowS test _ a = showDict a main = putStrLn $ (test ShowHex 27) "" Keean. Keean Schupke wrote:
Easy:
data ShowHex a instance Show (ShowHex a) where showsPrec _ (ShowHex a) = showHex a
main = putStrLn $ (show (ShowHex 27))
Here, with labelled instances you would write:
show ShowHex 27
instead you write:
show (ShowHex 27)
Keean.
George Russell wrote:
Keean Schupke wrote:
Do you need a language extension at all? You can certainly do it with the existing extensions!
data ShowDict a instance Show (ShowDict a) where showsPrec _ (ShowDict a) = ...
I don't understand. How does that help you to, for example, use a function which requires Show Int but (say) substitute the standard function for which which shows in hexadecimal?
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Mon, Nov 15, 2004 at 12:31:33PM +0000, Keean Schupke wrote:
Easy: Here, with labelled instances you would write:
show ShowHex 27
instead you write:
show (ShowHex 27)
What about Ints buried deep in more complicated data structures: show ShowHex [[1, 2, 3], [4]] vs. show (map (map ShowHex) [[1, 2, 3], [4]]) BTW, This thread reminds me a similar problem that was discussed on haskell list. Unfortunately, there were some problems with this approach (accumulating typeclass contexts). http://www.haskell.org/pipermail/haskell/2004-August/thread.html#14427 Best regards, Tom -- .signature: Too many levels of symbolic links
Tomasz Zielonka wrote:
BTW, This thread reminds me a similar problem that was discussed on haskell list. Unfortunately, there were some problems with this approach (accumulating typeclass contexts).
http://www.haskell.org/pipermail/haskell/2004-August/thread.html#14427
Yes, quite relevant. In "Scrap your boilerplate", we had the strong version of this problem. Say, one would want to provide type-specific instances for some generic function, which however seemed impossible in the view of the Data methods whose types only mention the Data (and Typeable) class. (So while run-time type case via cast, mkT, etc. would be easy, it was unclear how to do static and modular type-case.) The problem is solved now! The Data class subclasses a dedicated customisation class, we call this class Property for now. This is a two-parameter class; one param for the property (which is a type constructor of kind * -> *), and another for the type at hand. The property class context is propagated during descent into substructures by suitably constraining the type of the method gfoldl. The general pattern of class context parameterisation is of course not restricted to SYB. The SYB site (FAQ, last item) provides details including a longer code snippet. Paper forthcoming. Ralf
George Russell wrote:
I like the idea too, not just for Show but for any instances. It seems to me that in general you should be able to combine the convenience of the Haskell type system with the power of Standard ML's structures and functors. Something along these lines was done by Kahl & Scheffczyk ("Named Instances for Haskell Type Classes", Haskell Workshop 2001). [..] (3) a way of using the dictionary. For this we need (appFn), declaraed by the type declaration. appFn has the unorthodox type
appFn :: ShowDict a -> (forall a . Show a => b) -> b
I've wanted this too. Amongst other things, it would subsume all the "fooBy" functions in the prelude and libraries - instead of having both sort and sortBy, you could just build an Ord dictionary and pass it to sort. Note that there should be a way of creating a dictionary without creating an ordinary instance - in the OP's application, you would want two intShowDicts, one of which showed in decimal and the other of which showed in hex. In Haskell you can't have two instance declarations for the same type... --KW 8-)
George Russell wrote:
I like the idea too, not just for Show but for any instances. It seems to me that in general you should be able to combine the convenience of the Haskell type system with the power of Standard ML's structures and functors.
It looks like it would be easy, but it's very hard. The reason type classes work so well right now is that they have a straightforward interpretation as restrictions on the universe of quantification of a type variable. When we translate Haskell to System F, dictionaries come along for the ride as run-time representatives of types. If more than one dictionary is allowed per type, this correspondence breaks down, and all hell breaks loose as a result. We've already seen this happen with implicit parameters. In a program with implicit parameters: * Beta conversion no longer preserves semantics. * The monomorphism restriction is no longer a restriction: it sometimes silently changes the meaning of a program. * Adding type signatures for documentation is no longer safe, since they may silently change the behavior of the program. * It's not even safe in general to add a signature giving the same type that the compiler would infer anyway: there are (common) cases in which this too changes the program's meaning. I ran into this quite by accident the first time I tried to use implicit parameters, and it was enough to scare me away from ever trusting them again. Your proposal, simple though it seems, would extend all of these problems to type classes. Since it hasn't been mentioned yet I should also point people once again to "Functional Pearl: Implicit Configurations" by Oleg and Chung-chieh Shan, which ingeniously uses polymorphic recursion to construct type class instances at run time. If there's a safe and sane way to add local dictionaries to the language, it's probably along those lines. -- Ben
Ben Rudiak-Gould wrote (snipped):
If more than one dictionary is allowed per type, this correspondence breaks down, and all hell breaks loose as a result. We've already seen this happen with implicit parameters. In a program with implicit parameters:
* Beta conversion no longer preserves semantics.
* The monomorphism restriction is no longer a restriction: it sometimes silently changes the meaning of a program.
* Adding type signatures for documentation is no longer safe, since they may silently change the behavior of the program.
* It's not even safe in general to add a signature giving the same type that the compiler would infer anyway: there are (common) cases in which this too changes the program's meaning. I ran into this quite by accident the first time I tried to use implicit parameters, and it was enough to scare me away from ever trusting them again.
Sorry, but I like implicit parameters, I use them, and I'm not going to stop using them because beta conversion no longer preserves semantics. The other objections don't bother me either; I add type signatures always for external functions (and would be quite happy if not doing so were an error).
Since it hasn't been mentioned yet I should also point people once again to "Functional Pearl: Implicit Configurations" by Oleg and Chung-chieh Shan, which ingeniously uses polymorphic recursion to construct type class instances at run time. If there's a safe and sane way to add local dictionaries to the language, it's probably along those lines.
It is very ingenious to encode complex configuration information by chains of types, but it is something I recoil from in horror.
George Russell wrote:
Since it hasn't been mentioned yet I should also point people once again to "Functional Pearl: Implicit Configurations" by Oleg and Chung-chieh Shan, which ingeniously uses polymorphic recursion to construct type class instances at run time. If there's a safe and sane way to add local dictionaries to the language, it's probably along those lines.
It is very ingenious to encode complex configuration information by chains of types, but it is something I recoil from in horror.
Yes, but I think the point is that local instances make sense since you can encode them like this. And if they make sense it might be a good idea to add them to make life less encoded. :) -- Lennart
George Russell wrote:
Sorry, but I like implicit parameters, I use them, and I'm not going to stop using them because beta conversion no longer preserves semantics.
You'll find that many people here don't agree with this view in general (though there's been surprisingly little backlash against implicit parameters in particular). The idea of allowing local dictionaries is as old as type classes. It's a more obvious idea than what Haskell actually does, in fact. As far as I know, Haskell would have had local dictionaries from the beginning but for problems like those I listed. You won't be able to persuade the community to adopt them unless you can find a way to keep the various worms in the can.
It is very ingenious to encode complex configuration information by chains of types, but it is something I recoil from in horror.
Lennart already answered this one. The part about round-tripping the runtime values through the type system, though ingenious, is not the part of the paper I want to preserve. :-) -- Ben
On Tuesday 16 Nov 2004 1:52 pm, Ben Rudiak-Gould wrote:
George Russell wrote:
Sorry, but I like implicit parameters, I use them, and I'm not going to stop using them because beta conversion no longer preserves semantics.
You'll find that many people here don't agree with this view in general (though there's been surprisingly little backlash against implicit parameters in particular).
I would like to lash against them. I was unaware of the problems you describe, but their existence doesn't surprise me. In view of the controversy that a certain other perfectly safe, reasonable (and necessary) language extension proposal has generated recently the ready acceptance of this kind of brokeness is surprising. Frankly, the idea that anyone would want to jump through hoops to add them to a purely functional language sounds bizarre to me. Safe beta conversion really ought to be a sacred cow. Still, at least they're not enabled by default. With any luck we won't see too many people shooting themselves in the foot because they're too lazy to pass their parameters explicitly. :-) Regards -- Adrian Hey
On Friday 19 November 2004 08:54, Adrian Hey wrote:
On Tuesday 16 Nov 2004 1:52 pm, Ben Rudiak-Gould wrote:
George Russell wrote:
Sorry, but I like implicit parameters, I use them, and I'm not going to stop using them because beta conversion no longer preserves semantics.
You'll find that many people here don't agree with this view in general (though there's been surprisingly little backlash against implicit parameters in particular).
I would like to lash against them. I was unaware of the problems you describe, but their existence doesn't surprise me. In view of the controversy that a certain other perfectly safe, reasonable (and necessary) language extension proposal has generated recently the ready acceptance of this kind of brokeness is surprising. Frankly, the idea that anyone would want to jump through hoops to add them to a purely functional language sounds bizarre to me. Safe beta conversion really ought to be a sacred cow.
Still, at least they're not enabled by default. With any luck we won't see too many people shooting themselves in the foot because they're too lazy to pass their parameters explicitly.
Implicit parameters are evil, agreed. Their deficiencies should be added as a warning to the docs (with many exclamation marks). But toplevel things with identity (TWI) are evil as well, *especially* if they are easy to use. Implicit parameters at least have the advantage that they are obscure and require changes to function signatures, so that (hopefully) not many people use them. The toplevel '<-' bindings proposal encourages bad library and program design by making TWIs easy and (apparently) safe. It is better to make people think hard about how to avoid them in the first place. (BTW, toplevel stdin, stdout, and stderr are evil too.) I know of exactly one good reason to use TWIs in Haskell. Which is: to interface C libraries that are broken because of the fact that TWIs are so easy to create in C. Introducing TWIs in Haskell is like deliberately spreading a disease into an area that has avoided it up to now by strict quarantine measures. Of course these measures are often inconvenient and some effort is required in order to make communication with the ill populace possible. And of course every now and then people come along complaining that everything would be easier if one would just remove all those decontamination barriers... Cheers, Ben
On Friday 19 Nov 2004 2:27 pm, Benjamin Franksen wrote:
Implicit parameters are evil, agreed. Their deficiencies should be added as a warning to the docs (with many exclamation marks).
Well I dunno. Maybe whatever's currently wrong with them can be fixed up. But I can't say they're something I've ever felt a need for. But it's ironic that some folk advocate the use of this (mis?)feature as a solution to the (so-called) "global variables" problem. I don't like this idea at all, but at least they recognise that there is a problem.
But toplevel things with identity (TWI) are evil as well, *especially* if they are easy to use.
Just repeating this again and again doesn't make it any more true. Neither you or any of the other nay-sayers have provided any evidence or credible justification for this assertion, nor have any of you provided any workable alternative for even the simplest example. Lennart has yet to explain how he proposes to implement his supposedly safer "openDevice". You have yet to explain how you propose to deal with stdout etc.. BTW, top level TWI's are easy to create anyway, via the *unsound* unsafePerformIO hack. The evil here not their existance, it is the unsoundness of their creation mechanism. Given that in the absence of anything better folk are going to continue to use this (because it really is necessary sometimes), objecting to the provision of a sound alternative is just silly. This is the "militant denial" I was talking about. And of course there's one top level TWI that none of us can live without. I am refering to the unique and stateful "world" that is implicitly referenced by all IO operations (with the possible exception of those operations I would like to put in the "SafeIO" monad). So is this evil too? Perhaps it is, but if so, I'd like to know how you propose to live without it and what purpose the IO monad would serve in such a situation. Regards -- Adrian Hey
Adrian Hey wrote:
Just repeating this again and again doesn't make it any more true.
Ditto... I for one think the best solution is to use the language as intended and pass the values as function arguments. As pointed out on this list - the only possible situation where you cannot do this is when interfacing to a badly written C library. In which case do your one-shot initialisation in C, as you will be importing foreign functions anyway.
Neither you or any of the other nay-sayers have provided any evidence or credible justification for this assertion, nor have any of you provided any workable alternative for even the simplest example. Lennart has yet to explain how he proposes to implement his supposedly safer "openDevice". You have yet to explain how you propose to deal with stdout etc..
openDevice would use OS semaphores (like the namedSem library I posted to the cafe) - the OS is the only thing that can deal with device driver initialisations. Infact the OS driver should multiplex single access devices such that access is serialised. I guess stdin, stdout etc should be passed to main as arguments like you would any other file handle. Although if file handles are simply Ints, then there is nothing wrong with having: stdin = 0 stdout = 1 stderr = 2 In this case they are not IO actions anyway.
And of course there's one top level TWI that none of us can live without. I am refering to the unique and stateful "world" that is implicitly referenced by all IO operations (with the possible exception of those operations I would like to put in the "SafeIO" monad). So is this evil too? Perhaps it is, but if so, I'd like to know how you propose to live without it and what purpose the IO monad would serve in such a situation.
This is true - but only because unsafePerformIO exists. Without it World is simply a value passed via the IO Monad. I would ask an alternative question - is it possible to live without unsafePerformIO? I have never needed to use it! Keean
On Monday 22 Nov 2004 11:26 am, Keean Schupke wrote:
Adrian Hey wrote:
Just repeating this again and again doesn't make it any more true.
Ditto... I for one think the best solution is to use the language as intended and pass the values as function arguments.
I guess you mean the usual handle based approach, but this makes no sense at all for a Haskell interface to some *unique* stateful resource (eg. a piece of raw hardware or "badly designed" C library). The handle is a completely redundant argument to all interface functions (there's no need identify which thing is being referenced because there is only one). Furthermore it still leaves you with the problem ensuring that users don't use whatever "openUniqueThing" routine that creates and initialises the state multiple times to end up with two or more different TWIs which are all trying to reflect state changes in the same unique resource. AFAICS the only way of preventing this requires the use of top level mutable state, so this solution is a non-solution (the only safe way of using this "solution" still leaves you with the original problem). Of course this problem could be solved quite simply with a top level.. userOpenUniqueThing <- oneShot openUniqueThing Unfortunately this is not an option because it creates a top level MVar (and so it must be evil). The only thing to be said in favour of the handle based approach is that forcing users to get the state handle does ensure that any necessary external initialisation has been performed prior to using the resource. But there are other ways to do this too (like prefixing every exported interface function with "userInit" instead of exporting userInit itself).
As pointed out on this list - the only possible situation where you cannot do this is when interfacing to a badly written C library.
This is one situation, but certainly not the only possible one. You have the same problem with interfacing to any unique stateful resource (or even if you have a multiple but finite supply of these resources).
This is true - but only because unsafePerformIO exists. Without it World is simply a value passed via the IO Monad.
Huh? Top level TWIs are just part of the initial world state (as seen by main). We can argue about whether or not they are needed, but their existence surely doesn't make the situation any worse than it already is.
I would ask an alternative question - is it possible to live without unsafePerformIO?
Not at present.
I have never needed to use it!
I have a feeling that those folk who think they don't need it are those who enjoy the luxury of doing all their IO via pre-supplied "Haskell user friendly" libraries and haven't given much thought to how these libraries actually work or how they could be implemented in Haskell if they didn't already exist (without using the unsafePerformIO hack of course). Regards -- Adrian Hey
Adrian Hey wrote:
I guess you mean the usual handle based approach, but this makes no sense at all for a Haskell interface to some *unique* stateful resource (eg. a piece of raw hardware or "badly designed" C library). The handle is a completely redundant argument to all interface functions (there's no need identify which thing is being referenced because there is only one).
Hopefully my last post laid the raw hardware device example to rest... It really is not necessary if coding a 'pure haskell' driver for some hardware as part of a Haskell OS. If the OS is not in Haskell your second example reduces to your first, if the OS is not serialising devices access we are just talking about interfacing with a badly written C library again.
This is one situation, but certainly not the only possible one. You have the same problem with interfacing to any unique stateful resource (or even if you have a multiple but finite supply of these resources).
No you don't... Most devices have registers, those registers contain values, you can inspect those values to see if the device has been initialised. You can then write a guard on the initialisation that really checks if the device has (or hasn't) been initialised rather than rely on some 'shadow' copies in RAM.
Huh? Top level TWIs are just part of the initial world state (as seen
by main). We can argue about whether or not they are needed, but their existence surely doesn't make the situation any worse than it already is.
The IO monad passes its state around, it is just hidden. This is not like implicit parameters. So main is _passed_ RealWorld as an argument, and returns RealWorld, just like the state monad: data State a = State (s -> (s,a)) Hides the passing of the state...
I have a feeling that those folk who think they don't need it are those who enjoy the luxury of doing all their IO via pre-supplied "Haskell user friendly" libraries and haven't given much thought to how these libraries actually work or how they could be implemented in Haskell if they didn't already exist (without using the unsafePerformIO hack of course).
No, if that were the case I would be suggesting we can do without unsafeInterleaveIO... but unfortunately it seems necessary to me. Keean.
On Tuesday 23 November 2004 10:39, Keean Schupke wrote:
Adrian Hey wrote:
This is one situation, but certainly not the only possible one. You have the same problem with interfacing to any unique stateful resource (or even if you have a multiple but finite supply of these resources).
No you don't... Most devices have registers, those registers contain values, you can inspect those values to see if the device has been initialised. You can then write a guard on the initialisation that really checks if the device has (or hasn't) been initialised rather than rely on some 'shadow' copies in RAM.
Alas, unfortunately not every device is designed in this way (I can give examples if you want). Adrian is right in that there is not only badly designed C libraries but also badly designed hardware! Ben -- Top level things with identity are evil. -- Lennart Augustsson
Okay - but then you can keep state in haskell by using a driver thread and channels like in the example I posted. I guess I should have said it is best practice to check the real state rather than a (possibly wrong) copy. Keean. Benjamin Franksen wrote:
On Tuesday 23 November 2004 10:39, Keean Schupke wrote:
Adrian Hey wrote:
This is one situation, but certainly not the only possible one. You have the same problem with interfacing to any unique stateful resource (or even if you have a multiple but finite supply of these resources).
No you don't... Most devices have registers, those registers contain values, you can inspect those values to see if the device has been initialised. You can then write a guard on the initialisation that really checks if the device has (or hasn't) been initialised rather than rely on some 'shadow' copies in RAM.
Alas, unfortunately not every device is designed in this way (I can give examples if you want). Adrian is right in that there is not only badly designed C libraries but also badly designed hardware!
Ben
On Monday 22 November 2004 09:38, Adrian Hey wrote:
On Friday 19 Nov 2004 2:27 pm, Benjamin Franksen wrote:
But toplevel things with identity (TWI) are evil as well, *especially* if they are easy to use.
Just repeating this again and again doesn't make it any more true. Neither you or any of the other nay-sayers have provided any evidence or credible justification for this assertion, nor have any of you provided any workable alternative for even the simplest example.
This is getting ridiculous. At least two workable alternatives have been presented: - C wrapper (especially if your library is doing FFI anyway) - OS named semaphores Further, as for "evidence or credible justification" for the my claim, you can gather it from the numerous real-life examples I gave, and which you chose to ignore or at least found not worthy of any comment. Of course, these examples are only annecdotal, but I think this is better than a completely artificial requirement (like your "oneShot"). You have been asked more than once to present a *real-life* example to illustrate that (a) global variables are necessary (and not just convenient), (b) both above mentioned alternatives are indeed unworkable.
You have yet to explain how you propose to deal with stdout etc..
I see absolutely no reason why stdxxx must or should be top-level mutable objects. They can and should be treated in the same way as environment and command line arguments, i.e. getArgs :: IO [String] getEnv :: String -> IO String getStdin, getStdout, getStderr :: IO Handle Note that (just like environment and command line arguments) these handles may refer to completely different things on different program runs. Ben -- Top level things with identity are evil. -- Lennart Augustsson
On 2004-11-22, Benjamin Franksen <benjamin.franksen@bessy.de> wrote:
On Monday 22 November 2004 09:38, Adrian Hey wrote:
You have yet to explain how you propose to deal with stdout etc..
I see absolutely no reason why stdxxx must or should be top-level mutable objects. They can and should be treated in the same way as environment and command line arguments, i.e.
getArgs :: IO [String] getEnv :: String -> IO String getStdin, getStdout, getStderr :: IO Handle
Note that (just like environment and command line arguments) these handles may refer to completely different things on different program runs.
Er, no. The handles can be considered as the same but _pointing_ to different things on different runs. Keeping them outside the IO monad, and only accessing them inside -- i.e. the current situation -- would be fine. They're not mutable in any sense. -- Aaron Denney -><-
On Tuesday 23 November 2004 00:10, Aaron Denney wrote:
On 2004-11-22, Benjamin Franksen <benjamin.franksen@bessy.de> wrote:
On Monday 22 November 2004 09:38, Adrian Hey wrote:
You have yet to explain how you propose to deal with stdout etc..
I see absolutely no reason why stdxxx must or should be top-level mutable objects. They can and should be treated in the same way as environment and command line arguments, i.e.
getArgs :: IO [String] getEnv :: String -> IO String getStdin, getStdout, getStderr :: IO Handle
Note that (just like environment and command line arguments) these handles may refer to completely different things on different program runs.
Er, no. The handles can be considered as the same but _pointing_ to different things on different runs.
I wrote "may refer to", not "are", so yes.
Keeping them outside the IO monad, and only accessing them inside -- i.e. the current situation -- would be fine.
I beg to differ. Note, I do not claim they are unsafe.
They're not mutable in any sense.
Well, a variable in C is not mutable in exactly the same sense: It always refers (="points") to the same piece of memory, whatever value was written to it. Where does that lead us? Ben -- Top level things with identity are evil. -- Lennart Augustsson
On 2004-11-23, Benjamin Franksen <benjamin.franksen@bessy.de> wrote:
On Tuesday 23 November 2004 00:10, Aaron Denney wrote:
On 2004-11-22, Benjamin Franksen <benjamin.franksen@bessy.de> wrote:
On Monday 22 November 2004 09:38, Adrian Hey wrote:
You have yet to explain how you propose to deal with stdout etc..
I see absolutely no reason why stdxxx must or should be top-level mutable objects. They can and should be treated in the same way as environment and command line arguments, i.e.
getArgs :: IO [String] getEnv :: String -> IO String getStdin, getStdout, getStderr :: IO Handle
Note that (just like environment and command line arguments) these handles may refer to completely different things on different program runs.
Er, no. The handles can be considered as the same but _pointing_ to different things on different runs.
I wrote "may refer to", not "are", so yes.
They're wrappers around the integers 0, 1, and 2. The handles could have been implemented to be the same, at each invocation. (I expect they are in most implementations). If we had to make them ourselves, they could be done as: stdin = makeHandle 0 stdout = makeHandle 1 stderr = makeHandle 2 in absolutely pure Haskell, only the things that manipulate them need be in the IO monad. They're not the external state in the world to which they point -- just ways of instructing the OS. I don't see how sprinkling "stdin <- getStdin" in every IO routine helps at all. The means of instructing the OS is a constant. Unlike the case with IORefs, we have an extra argument that keeps the compiler from aliasing these together. The arguments and environment really are different at each invocation, not merely referring to different things. If you add a redirection, the "argument space" and "environment space" at invocation (barring relocation) would be the same. But since there's only one, it's easier to provide access functions with the argument of where to look already applied.
Keeping them outside the IO monad, and only accessing them inside -- i.e. the current situation -- would be fine.
I beg to differ. Note, I do not claim they are unsafe.
If it's not unsafe, and it makes for simpler (hence easier to understand, create, debug, and modify) in what sense is it not fine?
They're not mutable in any sense.
Well, a variable in C is not mutable in exactly the same sense: It always refers (="points") to the same piece of memory, whatever value was written to it. Where does that lead us?
A slightly different sense, but I won't quibble much. It would lead us to being able to have TWIs, only readable or writeable in the IO Monad. Many people don't think that would be such a bad thing. But because of the semantics we expect from IORefs, we can't get them without destroying other properties we want. a = unsafePerformIO $ newIORef Nothing Respecting referential integrity would give us the wrong semantics. Adding labels would force the compiler to keep two differently labeled things seperate, but it would fall down for things having the same label. a = unsafePerformIO $ newLabeledIORef "a" Nothing b = unsafePerformIO $ newLabeledIORef "a" Nothing If we look at this, we could legitimately expect them to either be unified, or not be unified, but we would want consistency. Doing this uniformly seems a tough burden on compiler writers. In contrast with IO Handles, there the OS does all the work. If "makeHandle" were exposed to us, It really wouldn't matter whether handle1 and stdout handle1 = makeHandle 1 stdout = makeHandle 1 were beta-reduced or not. Either way, the OS naturally handles how we refer to stdout. (One caveat here -- buffering implemented by the compiler & runtime would make a difference.) -- Aaron Denney -><-
On Monday 22 Nov 2004 4:03 pm, Benjamin Franksen wrote:
This is getting ridiculous. At least two workable alternatives have been presented:
- C wrapper (especially if your library is doing FFI anyway) - OS named semaphores
Neither of these alternatives is a workable general solution. There are several significant problems with both, but by far the most significant problem (at least if you believe that top level mutable state is evil) is that they both rely on the use of top level mutable state. If this is evil it is surely just as evil in C or OS supplied resources as it is in Haskell. The fact that one solution requires the use of a completely different programming language and the other requires the use of a library which could not be implemented in Haskell (not without using unsafePerformIO anyway) must be telling us that there something that's just plain missing from Haskell. IMO this is not a very satisfactory situation for a language that's advertised as "general purpose".
Further, as for "evidence or credible justification" for the my claim, you can gather it from the numerous real-life examples I gave, and which you chose to ignore or at least found not worthy of any comment.
I have no idea what examples you're talking about. Did you post any code? If so, I must have missed it for some reason. Perhaps your're refering to your elimination of unsafePerformIO from a library you were writing. It's not really possible to comment on the significance of you being able to eliminate top level mutable state in this case without knowing why you were using it in the first place.
Of course, these examples are only annecdotal but I think this is better than a completely artificial requirement (like your "oneShot").
Being able to avoid the use of top level mutable state sometimes (or even quite often) is not proof that it's unnecessary, especially when nobody (other than yourself presumably) knows why you were using it in the first place. However, the existance of just one real world example where it does appear unavoidable is pretty convincing evidence to the contrary IMO. It may yet prove to be avoidable, but nobody has managed to show that and I certainly can't think of a way.
You have been asked more than once to present a *real-life* example to illustrate that
(a) global variables are necessary (and not just convenient), (b) both above mentioned alternatives are indeed unworkable.
I knew this would happen. I was asked to provide an example and I *did*. I gave the simplest possible example I had of the more general problem, and now this whole thread has consisted of either repeated denials of the reality of even this simple problem (something you've just done again) or protracted discussions over various half baked non-solutions to this one particular problem (such as those you identify above) without seeing the real underlying general problem. (See my response to Keaan) You have the same basic problem when dealing with any unique stateful resource. Even the state handle passing solution that I believe yourself, Keaan and Lennart would advocate is unsafe without using top level mutable state one way or another (a problem that could be fixed quite easily by using "oneShot" at the top level I might add).
You have yet to explain how you propose to deal with stdout etc..
I see absolutely no reason why stdxxx must or should be top-level mutable objects. They can and should be treated in the same way as environment and command line arguments, i.e.
getArgs :: IO [String] getEnv :: String -> IO String getStdin, getStdout, getStderr :: IO Handle
Note that (just like environment and command line arguments) these handles may refer to completely different things on different program runs.
Sure, Peter Simons suggested the same thing. I have no great objection, but why do this? I mean what extra safety does this buy you? Anybody can still get at stdout and write anything they like to it. The only difference is that instead of writing.. do ... foo stdout ... ..they now have to write.. do ... stdout <- getStdout foo stdout ... I don't see why the former should be regarded as a source of great evil which is somehow eliminated by the latter. Regards -- Adrian Hey
On Mon, Nov 22, 2004 at 05:03:30PM +0100, Benjamin Franksen wrote:
You have been asked more than once to present a *real-life* example to illustrate that
(a) global variables are necessary (and not just convenient), (b) both above mentioned alternatives are indeed unworkable.
First of all, there are a couple issues here that are getting mixed up in the discussion. One is that no one is arguing for everyone to use global variables, their disadvantages are well known. However, we do see a practical NEED for a mechanism in the language to create top-level monadic bindings. The existance of other mechanisms to achieve the same thing only STRENGTHENS the argument for them, they won't break anything because we already have them, but will be actually safe, rather than coincidentally safe due to the peculiarities of the ghc optimizer and absurdly more efficient. Motivated. I decided to do some grepping and bring up some examples. surprisingly, there is a perfect example in the haskell standard itself: **** module Random where setStdGen :: StdGen -> IO () getStdGen :: IO StdGen randomIO :: Random a => IO a These get and set the global standard generator for random numbers. Random number algorithms are easy to write in haskell, generator splitting routines, the random number generation happens in the IO monad so it is even okay that it depends on other calls to randomIO and setStdGen but THIS CANNOT BE IMPLEMENTED IN HASKELL. and that is the problem. Should one really have to pass the global standard generator around? people write monads for the express purpose of HIDING this sort of thing and IO is a great ubiquitous monad, it would be a shame if a user couldn't extend it to also pipe around an appropriate random number seed. **** stdout,stderr,stdin - people have brought up that these can be represented by stdout = 1, stdin = 0, and stderr = 2 and treat the integers as refering to magic built-in constants that refer to handles. now, what if you wanted to stop relying on magic built-ins and implement your buffering algorithms in haskell directly exposing them to ghcs optimizer? You can't without the ability to globally initialize their buffers. **** (some examples from libraries) Data.Unique This provides a unique supply of numbers in the IO Monad and illustrates the efficiency concerns extremely well. newUnique :: IO Int this creates a new unique integer simply by incrementing a number and returning it. the number is a part of the world. now, we can in theory implement this without top level declarations: newUnique = do e <- getEnv "magicUniqueName" let n = (read e :: Int) putEnv "magicUniqueName" (show $ n + 1) return n (or perhaps something based on files) note, this does exactly the same thing, but what should have been 3 machine instructions is now thousands and thousands and is much less safe because someone else might guess the 'magicUniqueName' and overwrite it, if we had a top-level var, we could just not export the var and rest assured our invarients are not broken. **** Atom.hs from ginsu.. This is perhaps the best example, and an incredibly useful piece of code for anyone struggling with space problems out there. it provides data Atom = ... (abstract) instance Ord Atom instance Eq Atom toAtom :: String -> Atom fromAtom :: Atom -> String What it does is hash the strings in a global hash and return an Atom which internally has an index into a table of strings. This has a couple of advantages: comparing Atoms is much much faster, equivalant to comparing Ints, the strings are hash-consed so all instances of "Foo" will use the same memory. internally, Atom has a global hash table of strings -> atoms, note that externally, Atom is truly purely functional. toAtom and fromAtom although using internal state inside are real functions. the same argument always returns the same (externally visible) result. This is because the actual integer chosen is hidden, there is no way to get at it outside the module. there would be no way to do this without global state without seriously compromising it's usability. imagine I decided to get rid of the global state because I (mistakenly) believed that all global state was inherently evil no matter what. then my recourse would be to implement something like: data AtomHash = ... data Atom = ... (abstract) instance Ord Atom instance Eq Atom newAtomHash :: IO AtomHash toAtom :: AtomHash -> String -> IO Atom fromAtom :: AtomHash -> Atom -> IO String note a couple things: 1. The pure functions now are stuck in the IO monad, since I made their dependence on AtomHash explicit, the fact that they modify AtomHash must be made explicit by placing them in the IO monad. (it is possible to come up with other formulations not in the IO monad, but they would have similar problems) This alone is almost enough to kill the idea, but even worse is the second 2. The fundamental property that there is an isomorphism between Atoms and Strings is broken. because one might create multiple AtomHashs. Suddenly what was a STATIC COMPILE TIME GUARENTEE becomes a run-time obscure bug generating probelem. furthermore, imagine you carefully avoided ever creating more than one AtomHash, what purpose does it serve to pass everywhere then? it is meerly a source of confusion and obfuscation. and someone could come along to use your library, call 'newAtomHash' and break everything in a way that would be very tricky to debug. This is not a minor performance gain. in ginsu it dropped the memory usage from > 100megs to 10megs. I would call that vital. when it used 100megs it was not a usable program. **** Caching. in ginsu and a couple other projects, There is the common idiom where you have something that depends on IO and is very expensive to calculate. for the sake of argument, let us pretend processing your configuration files and command line arguments was very expensive. what you do is go and write data Config = ... getConfig :: IO Config now, you want to be able to call getConfig to get the configuration at various stages, however this is very expensive, it has to read files, parse them, etc.... note that getConfig is in the IO monad, we are doing nothing tricky, it is perfectly fine for this IO action to call getArgs and read files. but the problem is we have to do it every time, there is no way to cache the result of a previous run. with a global variable, all we need to do is set the variable and check the modification times of the files and re-run it if needed. suddenly, we get fast efficient configuration for the common unchanging case for free without changing the program semantics at all. getConfig behaves identically after the change, it is just more efficient. perhaps drastically so if it was doing something more complicated than reading config files. Why burden the user with this performance hack by making them explicitly carry the cache around? as far as the user is concerned, getConfig DOES go and read the config files each time, They see it is in IO, they know it can be doing anything, and logically, that is how they would like to think about it when writing their program, a black box, the ability to abstract away things like this is a great asset for a programming language. **** Haskell ALREADY is great at delegating stateful computation to the IO monad, this won't change that, it won't break any properties which arn't already breakable. in fact, it won't even break any properties at all, everything is STILL in the IO monad, anything that depends on global state will already HAVE to be in the IO monad, that should be indication enough to the programmer that this depends on the world, extended by the programmer in well thought out abstracted ways. John -- John Meacham - ⑆repetae.net⑆john⑈
On Tue, 23 Nov 2004 20:50:45 -0800, John Meacham <john@repetae.net> wrote:
On Mon, Nov 22, 2004 at 05:03:30PM +0100, Benjamin Franksen wrote:
You have been asked more than once to present a *real-life* example to illustrate that
(a) global variables are necessary (and not just convenient), (b) both above mentioned alternatives are indeed unworkable.
First of all, there are a couple issues here that are getting mixed up in the discussion. One is that no one is arguing for everyone to use global variables, their disadvantages are well known. However, we do see a practical NEED for a mechanism in the language to create top-level monadic bindings. The existance of other mechanisms to achieve the same thing only STRENGTHENS the argument for them, they won't break anything because we already have them, but will be actually safe, rather than coincidentally safe due to the peculiarities of the ghc optimizer and absurdly more efficient.
Motivated. I decided to do some grepping and bring up some examples.
Very nice survey of practical applications! To futher clarify the discussion, though, I'd like to note two distinct uses of unsafePerformIO: 1) encapsulating referentially transparent IO actions into pure functions 2) creating one-time-actions / top-level-mutable-variables / TWI's For example, in your example of Atom.hs, the top-level hashtable would be #2, while the pure external interface is #1. I believe your post addressed #2, please correct me if otherwise. I bring this up because there are two _separate_ debates happening on the haskell lists that are often confused or combined. Using the same numbering as above, they are: 1) whether values like getArgs or stdout are safe enough to be pure values (i.e., referentially transparent) 2) whether TWI's/etc are necessary, and if so whether to implement them - as a language extension like top-level (x <- someAction), or - as an all-purpose library (such as George Russell's recent post) using e.g. Dynamics or semaphores Does this seem like a fair summary? Best, -Judah Jacobson
On Wed, Nov 24, 2004 at 01:35:53AM -0500, Judah Jacobson wrote:
Very nice survey of practical applications! To futher clarify the discussion, though, I'd like to note two distinct uses of unsafePerformIO:
1) encapsulating referentially transparent IO actions into pure functions 2) creating one-time-actions / top-level-mutable-variables / TWI's
For example, in your example of Atom.hs, the top-level hashtable would be #2, while the pure external interface is #1. I believe your post addressed #2, please correct me if otherwise.
Ah yes, exactly. this is a very important point to bring up because the two uses are actually quite different in a fundamental way. #1 is safe-safe. in the fully safe meaning, as in all functional transformations one expects to be valid remain valid on a truely referentialy transparent pure function whether it is implemented internally with unsafePerformIO or not. This is exactly the proper use of unsafePerformIO. #2 unfortunatly, is the problem and I think the much more common use of unsafePerformIO because there does not exist another workaround in haskell for the top level initializer problem. It is an unsafe use of unsafePerformIO, meaning there are generally valid language transformations which change the meaning of programs using it and the fact it has worked so well is just a coincidence of implementation. The reason I only addressed #2 is because it is the only one that actually has a technical problem that needs to be addressed :) Part of my current interest in #2 is that I have been experimenting with some full-program optimization algorithms which could perhaps give substantial gains but would pretty much obliterate any uses of the unsafePerformIO global variable hack, and no pragma can save them. Before this, I never realized just how uncorrect the global variable use of unsafePerformIO was :) Thinking about this problem and realizing there is no in-language solution without giving up certain key optimizations is what originally motivated my previous concrete proposal based on the 'mdo' semantics. John -- John Meacham - ⑆repetae.net⑆john⑈
On Wed, Nov 24, 2004 at 01:34:31AM -0800, John Meacham wrote:
Part of my current interest in #2 is that I have been experimenting with some full-program optimization algorithms which could perhaps give substantial gains but would pretty much obliterate any uses of the unsafePerformIO global variable hack, and no pragma can save them. Before this, I never realized just how uncorrect the global variable use of unsafePerformIO was :)
That sounds fascinating. Can you say more about the optimizations in question, and why the hack is so incorrect? Peace, Dylan
On Tue, Nov 23, 2004 at 08:50:45PM -0800, John Meacham wrote:
Atom.hs from ginsu..
This is perhaps the best example, and an incredibly useful piece of code for anyone struggling with space problems out there.
it provides
data Atom = ... (abstract)
instance Ord Atom instance Eq Atom toAtom :: String -> Atom fromAtom :: Atom -> String
[...]
internally, Atom has a global hash table of strings -> atoms, note that externally, Atom is truly purely functional. toAtom and fromAtom although using internal state inside are real functions. the same argument always returns the same (externally visible) result. This is because the actual integer chosen is hidden, there is no way to get at it outside the module.
Just a nitpick: will this code always yield the same results? map fromAtom $ sort $ map toAtom $ words "Just a nitpick" Best regards, Tom
Tomasz Zielonka wrote:
On Tue, Nov 23, 2004 at 08:50:45PM -0800, John Meacham wrote:
Atom.hs from ginsu..
This is perhaps the best example, and an incredibly useful piece of code for anyone struggling with space problems out there.
it provides
data Atom = ... (abstract)
instance Ord Atom instance Eq Atom toAtom :: String -> Atom fromAtom :: Atom -> String
[...]
internally, Atom has a global hash table of strings -> atoms, note that externally, Atom is truly purely functional. toAtom and fromAtom although using internal state inside are real functions. the same argument always returns the same (externally visible) result. This is because the actual integer chosen is hidden, there is no way to get at it outside the module.
Just a nitpick: will this code always yield the same results?
map fromAtom $ sort $ map toAtom $ words "Just a nitpick"
This is one of the very few cases where I've used unsafePerformIO (because I too have implemented something like Atom :). To make this work properly you need to actually compare the strings for Ord, but you can compare "pointers" for Eq. Doing that, you don't break Haskell semantics (but proving it seems tricky). -- Lennart
Okay, I have reconsidered, and I think I would be happy with top-level TWI's providing they can be qualified on import, for example: module Main where import Library as L1 import Library as L2 main :: IO () main = do L1.do_something_with_library L2.do_something_with_library Keean. John Meacham wrote: [i've cut this becuse its long...]
data AtomHash = ... data Atom = ... (abstract)
instance Ord Atom instance Eq Atom newAtomHash :: IO AtomHash toAtom :: AtomHash -> String -> IO Atom fromAtom :: AtomHash -> Atom -> IO String
note a couple things:
1. The pure functions now are stuck in the IO monad, since I made their dependence on AtomHash explicit, the fact that they modify AtomHash must be made explicit by placing them in the IO monad. (it is possible to come up with other formulations not in the IO monad, but they would have similar problems) This alone is almost enough to kill the idea, but even worse is the second
2. The fundamental property that there is an isomorphism between Atoms and Strings is broken. because one might create multiple AtomHashs. Suddenly what was a STATIC COMPILE TIME GUARENTEE becomes a run-time obscure bug generating probelem.
furthermore, imagine you carefully avoided ever creating more than one AtomHash, what purpose does it serve to pass everywhere then? it is meerly a source of confusion and obfuscation. and someone could come along to use your library, call 'newAtomHash' and break everything in a way that would be very tricky to debug.
This is not a minor performance gain. in ginsu it dropped the memory usage from > 100megs to 10megs. I would call that vital. when it used 100megs it was not a usable program.
John Meacham wrote:
randomIO [...] Data.Unique [...] Atom.hs [...] caching
These are all great examples of cases where having per-process state makes sense. But they can all be implemented with George Russell's library plus safe (pure) uses of unsafePerformIO. I hope his library or something like it will become a part of the standard distribution, and there's nothing wrong with having (pure) functions in the standard library which can't be implemented in Haskell, so I don't think these examples are sufficient on their own to justify a language extension. I'd still like to see an example of something that can be done with top-level <- but is inconvenient or impossible with George Russell's library.
This is not a minor performance gain. in ginsu it dropped the memory usage from > 100megs to 10megs. I would call that vital. when it used 100megs it was not a usable program.
Not that I think implementing Atom with a global hashtable is a bad idea, but I'm curious where in that range the memory usage would be if you defined type Atom = PackedString toAtom = packString fromAtom = unpackPS -- Ben
Having admited to wavering on the edge of accepting top level TWIs, perhaps one of the supporters would like to comment on qualified importing... IE what happens to the unique property if I import 2 copies like so: module Main where import Library as L1 import Library as L2 Although each library's internal state is initialised once, as required, any real IO could lead to problems... With the device driver example I now have two bits of code that think they have exclusive access to the device... But I can do: L1.readFromDevice L2.readFromDevice Comments? Keean. Ben Rudiak-Gould wrote:
John Meacham wrote:
randomIO [...] Data.Unique [...] Atom.hs [...] caching
These are all great examples of cases where having per-process state makes sense.
But they can all be implemented with George Russell's library plus safe (pure) uses of unsafePerformIO. I hope his library or something like it will become a part of the standard distribution, and there's nothing wrong with having (pure) functions in the standard library which can't be implemented in Haskell, so I don't think these examples are sufficient on their own to justify a language extension. I'd still like to see an example of something that can be done with top-level <- but is inconvenient or impossible with George Russell's library.
This is not a minor performance gain. in ginsu it dropped the memory usage from > 100megs to 10megs. I would call that vital. when it used 100megs it was not a usable program.
Not that I think implementing Atom with a global hashtable is a bad idea, but I'm curious where in that range the memory usage would be if you defined
type Atom = PackedString toAtom = packString fromAtom = unpackPS
-- Ben
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Wed, Nov 24, 2004 at 03:48:56PM +0000, Keean Schupke wrote:
Having admited to wavering on the edge of accepting top level TWIs, perhaps one of the supporters would like to comment on qualified importing... IE what happens to the unique property if I import 2 copies like so:
module Main where
import Library as L1 import Library as L2
Although each library's internal state is initialised once, as required, any real IO could lead to problems... With the device driver example I now have two bits of code that think they have exclusive access to the device... But I can do:
Hmm? I am not really sure what you are asking. With my mdo proposal, and I think all proposals brought forth, the module system behaves identically to how it normally does for namespace control. top level initializers are just constant definitions for all most everything is concerned. modules do not change code at all, they are pure syntantic sugar for deciding what names you can see. i.e. it does not matter whether you do import List as L1 import List as L2 L1.sort and L2.sort refer to the same thing. it would be no different if sort were written with global state or even was a top level binding. John -- John Meacham - ⑆repetae.net⑆john⑈
John Meacham wrote:
With my mdo proposal, and I think all proposals brought forth, the module system behaves identically to how it normally does for namespace control. [...] modules do not change code at all, they are pure syntantic sugar for deciding what names you can see.
I'm not sure I understand your mdo proposal correctly then. A lot of this debate has been over what should happen when a module has a top-level action like x <- putStrLn "hello" Everyone agrees that "hello" should be printed at most once, and that if the value of x is ever demanded, it should be printed exactly once. But there's disagreement on everything else. What if I import the module containing the above declaration, but it can be proven statically that the value of x will never be demanded? What if it can't be proven statically, but it happens to be true on a particular run? If "hello" is printed even when x's value is not demanded, then import does more than bring names into scope: it also sometimes adds things to the top-level mdo. If "hello" is printed only when x's value is demanded, then import is okay but the <- construct is unsafe (though safer than unsafePerformIO). This kind of thing turned a lot of people off to the idea of top-level initialization actions. George Russell's proposal is appealing because it neatly avoids such problems. -- Ben
On Thursday 25 November 2004 00:38, Ben Rudiak-Gould wrote:
John Meacham wrote:
With my mdo proposal, and I think all proposals brought forth, the module system behaves identically to how it normally does for namespace control. [...] modules do not change code at all, they are pure syntantic sugar for deciding what names you can see.
I'm not sure I understand your mdo proposal correctly then. A lot of this debate has been over what should happen when a module has a top-level action like
x <- putStrLn "hello"
Everyone agrees that "hello" should be printed at most once, [...]
And I thought at least everyone agreed that things like that should not be allowed. Instead, only a "safe" subset of things that are currently in IO should be allowed to appear at the top-level, such as creation of mutable reference cells. Ben
On Thu, Nov 25, 2004 at 12:49:13AM +0100, Benjamin Franksen wrote:
On Thursday 25 November 2004 00:38, Ben Rudiak-Gould wrote:
John Meacham wrote:
With my mdo proposal, and I think all proposals brought forth, the module system behaves identically to how it normally does for namespace control. [...] modules do not change code at all, they are pure syntantic sugar for deciding what names you can see.
I'm not sure I understand your mdo proposal correctly then. A lot of this debate has been over what should happen when a module has a top-level action like
x <- putStrLn "hello"
Everyone agrees that "hello" should be printed at most once, [...]
And I thought at least everyone agreed that things like that should not be allowed. Instead, only a "safe" subset of things that are currently in IO should be allowed to appear at the top-level, such as creation of mutable reference cells.
Yes. I think such things should not be 'exported' by the default mechanism. just how that can be enforced by a library is an interesting technical issue. My current thinking is newtype InitIO a = InitIO (IO a) deriving(Monad) liftIO :: IO a -> InitIO a newInitIORef :: a -> InitIO (IORef a) newInitMVar ... then have x <- foo execute foo in the InitIO monad. The nice thing about this is that the 'hiding' mechanism is the normal haskell module system! by only exporting 'safe' IO actions as InitIO we effectivly enforce sane style while system programmers can go in and create new InitIO's as appropriate with liftIO. This is exactly equivalant to how the IO monad itself is handled. you get it abstractly by default, but if you know what you are doing you can extract out the RealWorld component in order to extend its functionality. I belive the only disadvantge to this scheme is the addition of another special name used in desugaring 'InitIO', however all the other proposals which involved limiting the set of initalization actions involved the same thing. (CIO, SafeIO, etc...) John -- John Meacham - ⑆repetae.net⑆john⑈
On Wed, Nov 24, 2004 at 11:38:42PM +0000, Ben Rudiak-Gould wrote:
John Meacham wrote:
With my mdo proposal, and I think all proposals brought forth, the module system behaves identically to how it normally does for namespace control. [...] modules do not change code at all, they are pure syntantic sugar for deciding what names you can see.
I'm not sure I understand your mdo proposal correctly then. A lot of this debate has been over what should happen when a module has a top-level action like
x <- putStrLn "hello"
Everyone agrees that "hello" should be printed at most once, and that if the value of x is ever demanded, it should be printed exactly once. But there's disagreement on everything else. What if I import the module containing the above declaration, but it can be proven statically that the value of x will never be demanded? What if it can't be proven statically, but it happens to be true on a particular run? If "hello" is printed even when x's value is not demanded, then import does more than bring names into scope: it also sometimes adds things to the top-level mdo. If "hello" is printed only when x's value is demanded, then import is okay but the <- construct is unsafe (though safer than unsafePerformIO).
There is some debate about this, and I think this is part of why there is so much confusion about what top-level-initializers are. First of all, I should say that I belive that stylistically x <- putStrLn "Hello" is a bad idea. it is just bad form to put observable actions in initializers IMHO, However, this does not necessarily mean it should be disallowed, because technically, there is nothing wrong with it. I think this causes problems because some people would like haskell to enforce good style, while at the moment I think it would be best to worry about technical problems. style can be enforced by libraries and command line switches to turn on-off features. 'unsafePerformIO' and unboxed types are not exported to the user by default because they probably don't need them and their use is in some sense bad styles. this doesn't mean they should be elided from the language, in fact, they are vital to many of the libraries internals and being able to write your own libraries as fast as system libraries is a necessary feature in any modern language. Now, my mdo proposal as written would have "hello" outputed exactly once at module start up time no matter what, whether x is demanded or not. it is equivalant to a program transformation that collects all the top level initializers and declarations, puts them all in a mdo block and runs it with the semantics explained in the fixIO paper. (with a deterministic, but partially undefined order) an argument can be made for changing it such that the IO action is only exectuted if its value is ever demanded, emulating the current unsafePerformIO hack. this is strictly less powerful than the above semanics, but some people like it better because in some sense the activity is not hidden and it preserves the non-trivial property that importing a module cannot change the programs behavior. (conversly, it does not allow imported modules to change programs behavior, which might be a useful feature for a special purpose debugging library which needs to do some setup for example) Personally, it does not matter too much which semantics are taken, as all my examples would work the same either way as will what I expect 99.99% of the uses of this feature. My guess is that it will come down to what is easier to implement for the compiler. the lazy IO has a lot of the same problems as the unsafePerformIO hack, but it at least offloads them to the compiler internals which can hopefully deal with them better. I think the mdo proposal would be ultimatly cleaner for a compiler writer and its semantics are better defined. the order actions are run while partially undefined is fixed at compile time, as opposed to depending on evaluation order. Also, the mdo way has the advantage of a known valid and implementable semantics rather than just the 'promise of the compiler' it won't do unsafe transformations in the lazy IO case.
This kind of thing turned a lot of people off to the idea of top-level initialization actions. George Russell's proposal is appealing because it neatly avoids such problems.
With one problem. His proposal cannot be implemented (efficiently) without top-level initialization actions. It is a potential user of TLIs, and very well might be the prefered interface for some but it is not a replacement. Just declaring it is done in the library by the system doesn't really avoid the issue when we are the ones writing the library and compiler too :) John -- John Meacham - ⑆repetae.net⑆john⑈
On Wednesday 24 Nov 2004 9:37 pm, John Meacham wrote:
On Wed, Nov 24, 2004 at 03:48:56PM +0000, Keean Schupke wrote:
Having admited to wavering on the edge of accepting top level TWIs, perhaps one of the supporters would like to comment on qualified importing... IE what happens to the unique property if I import 2 copies like so:
module Main where
import Library as L1 import Library as L2
Although each library's internal state is initialised once, as required, any real IO could lead to problems... With the device driver example I now have two bits of code that think they have exclusive access to the device... But I can do:
Hmm? I am not really sure what you are asking. With my mdo proposal, and I think all proposals brought forth, the module system behaves identically to how it normally does for namespace control. top level initializers are just constant definitions for all most everything is concerned.
modules do not change code at all, they are pure syntantic sugar for deciding what names you can see.
i.e. it does not matter whether you do
import List as L1 import List as L2
L1.sort and L2.sort refer to the same thing. it would be no different if sort were written with global state or even was a top level binding.
I think Keean is assuming the idea is that one should be able to duplicate top level TWIs by importing the same module twice. But of course this is not what's wanted. For example, it would enable users to short circuit the safety provisons of all the "oneShot" examples. If the purpose of a module is to allow users to have multiple distinct (top level or otherwise) TWIs then it should be exporting an appropriate newTWI constructor which is used in the usual manner by the importing module(s).. myTWI <- newTWI myOtherTWI <- newTWI Regards -- Adrian Hey
Thanks Adrian, for some reason I did not get the original reply to this post. This was my point, I may _want_ two copies of the library. Lets say I want to write a virtual machine emulator in haskell, and I then wish to use your library to drive the virtualised hardware... There must be some way to encapsulate the state requirement of the library such that the VM software can manage multiple states. At the momemt (with the handle approach) this is entirely possible: a <- initLibrary b <- initLibrary With you suggestion this VM cannot be written in Haskell any more. This is the point that I object to (and have been trying to explain badly). Perhaps the following extension would fix things: main = do a <- import Library -- a would be a record containing top level of library b <- import Library Keean. Adrian Hey wrote:
On Wednesday 24 Nov 2004 9:37 pm, John Meacham wrote:
On Wed, Nov 24, 2004 at 03:48:56PM +0000, Keean Schupke wrote:
Having admited to wavering on the edge of accepting top level TWIs, perhaps one of the supporters would like to comment on qualified importing... IE what happens to the unique property if I import 2 copies like so:
module Main where
import Library as L1 import Library as L2
Although each library's internal state is initialised once, as required, any real IO could lead to problems... With the device driver example I now have two bits of code that think they have exclusive access to the device... But I can do:
Hmm? I am not really sure what you are asking. With my mdo proposal, and I think all proposals brought forth, the module system behaves identically to how it normally does for namespace control. top level initializers are just constant definitions for all most everything is concerned.
modules do not change code at all, they are pure syntantic sugar for deciding what names you can see.
i.e. it does not matter whether you do
import List as L1 import List as L2
L1.sort and L2.sort refer to the same thing. it would be no different if sort were written with global state or even was a top level binding.
I think Keean is assuming the idea is that one should be able to duplicate top level TWIs by importing the same module twice. But of course this is not what's wanted. For example, it would enable users to short circuit the safety provisons of all the "oneShot" examples.
If the purpose of a module is to allow users to have multiple distinct (top level or otherwise) TWIs then it should be exporting an appropriate newTWI constructor which is used in the usual manner by the importing module(s)..
myTWI <- newTWI myOtherTWI <- newTWI
Regards -- Adrian Hey
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Thursday 25 Nov 2004 4:53 pm, Keean Schupke wrote:
Thanks Adrian, for some reason I did not get the original reply to this post.
This was my point, I may _want_ two copies of the library. Lets say I want to write a virtual machine emulator in haskell, and I then wish to use your library to drive the virtualised hardware... There must be some way to encapsulate the state requirement of the library such that the VM software can manage multiple states. At the momemt (with the handle approach) this is entirely possible:
a <- initLibrary b <- initLibrary
With you suggestion this VM cannot be written in Haskell any more. This is the point that I object to (and have been trying to explain badly).
Well it can be written in Haskell, but not using a module that was specifically designed to prevent this. You've gotta make a sensible choice as to what the purpose of module you're writing really is of course. But this is always the case I think, no magic bullets here. Regards -- Adrian Hey
Adrian Hey wrote:
Well it can be written in Haskell, but not using a module that was specifically designed to prevent this.
Well, It can be written in Haskell as it stands at the moment... This proposal would break that... You want the library programmer to have final say. I want the library user to have final say. An acceptable compromise would seem to be to make it the default that it can only be run once, but allow the 'world' to be encapsulated at a higher level of virtual machine... That is why I suggested the modified import syntax.
You've gotta make a sensible choice as to what the purpose of module you're writing really is of course. But this is always the case I think, no magic bullets here.
Exactly ... not. I think the user of the library should be free to (ab)use the library if they see fit. To me the property of encapsulation is more important than the ability to ensure a function gets run once and only once... If you want functions that can only be run once within a process, then a process must become a Haskell primitive, so that they can be manipulated, and encapsulated. Keean.
On Friday 26 Nov 2004 11:39 am, Keean Schupke wrote:
Adrian Hey wrote:
Well it can be written in Haskell, but not using a module that was specifically designed to prevent this.
Well, It can be written in Haskell as it stands at the moment...
No it can't. If I have a device driver that's accessing real hardware (peeking and poking specific memory locations say), how are you going to emulate that? You need to make peek and poke parameters of the module. That is certainly possible, but if the author of the driver module didn't anticipate your emulation needs, you'd be stuck I think. Regards -- Adrian Hey
But surely any device driver is parametrized on the exact IO addresses? How would you be able to handle multiple devices otherwise? Adrian Hey wrote:
On Friday 26 Nov 2004 11:39 am, Keean Schupke wrote:
Adrian Hey wrote:
Well it can be written in Haskell, but not using a module that was specifically designed to prevent this.
Well, It can be written in Haskell as it stands at the moment...
No it can't. If I have a device driver that's accessing real hardware (peeking and poking specific memory locations say), how are you going to emulate that? You need to make peek and poke parameters of the module.
That is certainly possible, but if the author of the driver module didn't anticipate your emulation needs, you'd be stuck I think.
Regards -- Adrian Hey
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Sunday 28 Nov 2004 6:44 pm, Lennart Augustsson wrote:
But surely any device driver is parametrized on the exact IO addresses? How would you be able to handle multiple devices otherwise?
Maybe, but this won't help hardware emulation (unless you're emulating RAM). Regards -- Adrian Hey
Adrian Hey wrote:
On Sunday 28 Nov 2004 6:44 pm, Lennart Augustsson wrote:
But surely any device driver is parametrized on the exact IO addresses? How would you be able to handle multiple devices otherwise?
Maybe, but this won't help hardware emulation (unless you're emulating RAM).
eh? You rewrite storable to emulate hardware - the IO address is about re-using the same driver for multiple physical devices. Keean.
On Tuesday 30 Nov 2004 11:23 am, Keean Schupke wrote:
Adrian Hey wrote:
On Sunday 28 Nov 2004 6:44 pm, Lennart Augustsson wrote:
But surely any device driver is parametrized on the exact IO addresses? How would you be able to handle multiple devices otherwise?
Maybe, but this won't help hardware emulation (unless you're emulating RAM).
eh? You rewrite storable to emulate hardware -
Perhaps that's an option sometimes.
the IO address is about re-using the same driver for multiple physical devices.
That was my point. Regards -- Adrian Hey
On Wed, Nov 24, 2004 at 02:40:52PM +0000, Ben Rudiak-Gould wrote:
John Meacham wrote:
randomIO [...] Data.Unique [...] Atom.hs [...] caching
These are all great examples of cases where having per-process state makes sense.
But they can all be implemented with George Russell's library plus safe (pure) uses of unsafePerformIO. I hope his library or something like it will become a part of the standard distribution, and there's nothing wrong with having (pure) functions in the standard library which can't be implemented in Haskell, so I don't think these examples are sufficient on their own to justify a language extension. I'd still like to see an example of something that can be done with top-level <- but is inconvenient or impossible with George Russell's library.
George Russell's library is precicly an invalid use of unsafePerformIO. Internally, it does the invalid unsafePerformIO (newIORef) trick which is exactly the problem we are trying to solve. hiding it in a module doesn't make it go away. I am not positive, but it also would also add the overhead of a finitemap lookup across all global variables for every look up. which doesn't really meet efficiency requirements. a global counter should only need a single peek poke to a constant location, not some data structure lookup. I spent a lot of time hand crafting a fast unboxed hash function for strings because that was a bottleneck in my Atom implementation, it would be a shame if all gains were overshadowed by what should be a very fast constant-time operation to begin with.
This is not a minor performance gain. in ginsu it dropped the memory usage from > 100megs to 10megs. I would call that vital. when it used 100megs it was not a usable program.
Not that I think implementing Atom with a global hashtable is a bad idea, but I'm curious where in that range the memory usage would be if you defined
type Atom = PackedString toAtom = packString fromAtom = unpackPS
Not very much. I had already writen my own utf-8 encoded PackedString with only moderate gain. due to the nature of the program a lot of the same unpredictable string is read in over the network, being able to unify their memory usage without too much hassle is a big big win. John -- John Meacham - ⑆repetae.net⑆john⑈
John Meacham wrote:
On Wed, Nov 24, 2004 at 02:40:52PM +0000, Ben Rudiak-Gould wrote:
But they can all be implemented with George Russell's library plus safe (pure) uses of unsafePerformIO.
George Russell's library is precicly an invalid use of unsafePerformIO. [...] hiding it in a module doesn't make it go away.
Yes it does. :-) If each Haskell environment ships with a correct implementation of the library, then its interface is the only part that matters. If the unsafePerformIO hack doesn't work in your new Haskell compiler, you can replace it with some other magic that does work. It's fine for the Haskell environment to hide impure magic behind a pure interface -- that's what the language is all about.
I am not positive, but it also would also add the overhead of a finitemap lookup across all global variables for every look up.
I'm not positive either, but I don't think it does: class InitialValue a where initialValue :: a uniqueRef :: (Typeable a, InitialValue a) => IORef a uniqueRef = unsafePerformIO (lookupWithRegister (newIORef initialValue)) ---- data MyType = ... deriving Typeable instance InitialValue MyType where initialValue = ... myEvilTopLevelTWI = (uniqueRef :: IORef MyType) No, I'm wrong. This can't possibly work, because the dictionaries are thread-local and can be rebound. The library should include another function with the same type as lookupWithRegister, but which uses a per-process dictionary which can't be rebound. Let's call it "oncePerType" since I think it's exactly the same as my oncePerType. Then replace lookupWithRegister by oncePerType in the definition of uniqueRef and it should work. But I've been wrong before. In fact I'm usually wrong in this thread. -- Ben
Ben Rudiak-Gould wrote:
Yes it does. :-) If each Haskell environment ships with a correct implementation of the library, then its interface is the only part that matters. If the unsafePerformIO hack doesn't work in your new Haskell compiler, you can replace it with some other magic that does work. It's fine for the Haskell environment to hide impure magic behind a pure interface -- that's what the language is all about.
What do you mean when you say the interface is pure? If your module is really pure then there should be an implemenation of it (which could have really bad complexity) with the same observable behaviour that uses only pure Haskell. Is this possible? If it's not possible I don't understand what you mean by pure. -- Lennart
Lennart Augustsson wrote:
What do you mean when you say the interface is pure?
If your module is really pure then there should be an implemenation of it (which could have really bad complexity) with the same observable behaviour that uses only pure Haskell. Is this possible?
Really? I agree with the converse of that statement, but I don't think it goes both ways. To me a function or module is pure when you can use it without compromising the equational properties of the language. I don't think Data.Dynamic or Control.Monad.ST satisfy your criterion for purity, but I would call them pure (after discarding the functions marked unsafe in the latter). -- Ben
Ben Rudiak-Gould wrote:
Lennart Augustsson wrote:
What do you mean when you say the interface is pure?
If your module is really pure then there should be an implemenation of it (which could have really bad complexity) with the same observable behaviour that uses only pure Haskell. Is this possible?
Really? I agree with the converse of that statement, but I don't think it goes both ways. To me a function or module is pure when you can use it without compromising the equational properties of the language. I don't think Data.Dynamic or Control.Monad.ST satisfy your criterion for purity, but I would call them pure (after discarding the functions marked unsafe in the latter).
Agreed, there can pure functions that cannot be written with the pure primitives, but then you have a proof obligation. An "easy" way to prove it is to provide an equivalent implementation that uses only pure functions. As far as I remember Control.Monad.ST can be written purely. And I think the same is true for Data.Dynamic. -- Lennart
Lennart Augustsson <lennart@augustsson.net> writes:
An "easy" way to prove it is to provide an equivalent implementation that uses only pure functions. As far as I remember Control.Monad.ST can be written purely. And I think the same is true for Data.Dynamic.
I think neither of them can. -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
-----Original Message----- From: haskell-bounces@haskell.org [mailto:haskell-bounces@haskell.org] On Behalf Of Marcin 'Qrczak' Kowalczyk Sent: den 25 november 2004 11:49 To: haskell@haskell.org Subject: Re: [Haskell] Real life examples
Lennart Augustsson <lennart@augustsson.net> writes:
An "easy" way to prove it is to provide an equivalent implementation that uses only pure functions. As far as I remember Control.Monad.ST can be written purely. And I think the same is true for Data.Dynamic.
I think neither of them can.
I agree with Marcin. I challenge you (Lennart) to write Control.Monad.ST in Haskell98. I (and many others) have tried and failed. An interesting summary by Koen can be found here: http://www.haskell.org/pipermail/haskell/2001-September/007922.html Cheers, /Josef
No, with exactly the type signatures they have I don't think you can. But the untyped version of them can be implemented. And that is good enough to convince me that the beta rule is still valid. -- Lennart Josef Svenningsson wrote:
-----Original Message----- From: haskell-bounces@haskell.org [mailto:haskell-bounces@haskell.org] On Behalf Of Marcin 'Qrczak' Kowalczyk Sent: den 25 november 2004 11:49 To: haskell@haskell.org Subject: Re: [Haskell] Real life examples
Lennart Augustsson <lennart@augustsson.net> writes:
An "easy" way to prove it is to provide an equivalent implementation that uses only pure functions. As far as I remember Control.Monad.ST can be written purely. And I think the same is true for Data.Dynamic.
I think neither of them can.
I agree with Marcin. I challenge you (Lennart) to write Control.Monad.ST in Haskell98. I (and many others) have tried and failed. An interesting summary by Koen can be found here: http://www.haskell.org/pipermail/haskell/2001-September/007922.html
Cheers,
/Josef
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Wed, Nov 24, 2004 at 10:40:41PM +0000, Ben Rudiak-Gould wrote:
John Meacham wrote:
On Wed, Nov 24, 2004 at 02:40:52PM +0000, Ben Rudiak-Gould wrote:
But they can all be implemented with George Russell's library plus safe (pure) uses of unsafePerformIO.
George Russell's library is precicly an invalid use of unsafePerformIO. [...] hiding it in a module doesn't make it go away.
Yes it does. :-) If each Haskell environment ships with a correct implementation of the library, then its interface is the only part that matters. If the unsafePerformIO hack doesn't work in your new Haskell compiler, you can replace it with some other magic that does work. It's fine for the Haskell environment to hide impure magic behind a pure interface -- that's what the language is all about.
That is exactly the problem we are trying to solve. coming up with a sane 'magic' which interacts well with all the semantics and program transforms we expect to be valid in a functional setting. the problem is the unsafePerformIO newIORef is NOT pure. it is broken by beta reduction among other things. This is precicely the problem, there are valid uses of unsafePerformIO where they hide a stateful implementation in a pure function, but global state is not one of them. There is a fundamental difference here between 'works because you have proven referential transparency and purity hold despite the use of unsafePerformIO' and 'works by accident' which is how george russel's and all stateful libraries using unsafePerformIO in that way work now. This is also why hiding it in a module does not help from a theoretical point of view, there is nothing keeping a cross-module optimization from propegating the 'unsoundness' everywhere throughout your program. So while you are correct in saying it is okay for the haskell environment to hide impure magic behind a pure interface, this is not an example of that. Also, there is a reason ghc exports things like unboxed types and RULES pragmas which no sane user should ever touch in theory, people need to be able to do system-type programming. The haskell libraries are written in haskell for example. Declaring that such a useful thing as global variables can only be implemented by the system in a magic library sort of goes against that and is a step backwards. or at least sideways. John Tangent: there is a logical truism that goes something vaugly like 'if 2 = 3 then I am king: the proof being, assume I am not king then 2 = 3 and 2 = 2 which is a contradiction therefore I must be king.' anyone know the actual eloquent formulation of this? It is just suposed to demonstrate that ANY contradiction anywhere makes anything provable and disprovable in a logical system, demonstrating why "just a little unsoundness" is not really an option... but my formulation doesn't sound quite right, does anyone know the proper one? -- John Meacham - ⑆repetae.net⑆john⑈
John Meacham wrote:
On Wed, Nov 24, 2004 at 10:40:41PM +0000, Ben Rudiak-Gould wrote:
If the unsafePerformIO hack doesn't work in your new Haskell compiler, you can replace it with some other magic that does work. It's fine for the Haskell environment to hide impure magic behind a pure interface -- that's what the language is all about.
That is exactly the problem we are trying to solve. coming up with a sane 'magic' which interacts well with all the semantics and program transforms we expect to be valid in a functional setting.
Ah ha. I see the source of confusion. I want the interface of George Russell's library to be /the/ way of creating top-level TWIs in Haskell, on which everything else is based, just as the various standard IO actions are /the/ way of doing I/O. I'm treating the implementation of his library as below the line, in the same category as GHC's internal state-threading implementation of IO. Yes, it needs special compiler support, but that support doesn't need to be standardized and doesn't need to be general. As such, the implementation doesn't need unsafePerformIO or NOINLINE, it only needs primReadRef1#, primWriteRef1#, primReadRef2#, and primWriteRef2#, which read and write the exactly two process-global refs supported natively by the runtime system. (Two because his implementation happens to use two global IORefs, but of course that could be reduced to one.) Everyone else who wants global IORefs gets them through the high-level interface built on those two primitive ones. You are treating the implementation of his library as above the line, and proposing a different primitive with which it can be implemented. Nothing wrong with that, but I claim it's not /necessary/, because his library alone is enough. -- Ben P.S. I'm getting tired of calling it "George Russell's library" -- can anyone suggest a better name?
Adrian Hey wrote:
But toplevel things with identity (TWI) are evil as well, *especially* if they are easy to use.
Just repeating this again and again doesn't make it any more true. Neither you or any of the other nay-sayers have provided any evidence or credible justification for this assertion, nor have any of you provided any workable alternative for even the simplest example. Lennart has yet to explain how he proposes to implement his supposedly safer "openDevice". You have yet to explain how you propose to deal with stdout etc..
Personally, I can't believe I hear people arguing for global variables. I thought that went away 30 years ago. It has nothing to do with functional programming. As for openDevice, if a device should only allow a single open I would assume this is part of the device driver in the operating system? (I know this is shifting blame. But I think it shifts it to where it belongs. In the OS there will be an "open" flag per device.) I admit there are proper uses of global variables, but they are very rare. You have not convinced me you have one. -- Lennart
On Mon, Nov 22, 2004 at 07:27:44PM +0100, Lennart Augustsson wrote:
[snip]
I admit there are proper uses of global variables, but they are very rare. You have not convinced me you have one.
-- Lennart
It's with some trepidation I bring a problem as a total newbie, but I've been obsessed with this and hung up on it ever since I decided a couple of weeks ago to learn Haskell by using it. Some brief background: A while back I decided I wanted a simple 'concept mapping' program that would work the way I work instead of the way someone else works. I envisioned a GUI with a canvas and an entry box (TK/tcl). I type a "concept name" into the entry box, and it shows up on the canvas (initially in a slightly randomized position), in a box, with a unique sequenced identifier. The identifier is also used as a canvas tag for the item. Similar input for relations between concepts. I think that's enough description for now. Initially, I started programming this with PerlTK, but that effort was interrupted for a few weeks. When I got back to it, I decided to do it in Python instead. But that effort also got interrupted for a few weeks. Before I got back to it, I ran across some material on Haskell I've had in my files for a few years, and decided that I'd use this as a vehicle to actually learn Haskell. (This all sounds a bit unfocused, and it is: I'm retired, sometimes describe myself as an ex mathematician or an ex-PhD having spent years in the aerospace industry instead of academia. Anyway, I have both the luxury and lack of focus of no deadlines, no pressure to publish. I hope to use Haskell to further my main hobby of knowledge representation.) In perl, my labels/tags were very easy: In the initialization code: my @taglist = (); my $nextag = "a"; and in the callback for the entry box: push(@taglist,$nextag); $nextag++; (With the starting tag of "a" this results in "a",...."z","aa","ab",...) Also, ultimately, I want to be able to save my work and restart the next day (say) picking up the tags where I left off. I'm darned if I can see how to do this in a callback without a global variables (and references from other callbacks, by the way). In looking for a method, I've discovered that Haskell is a lot richer than I thought (or learned when I tinkered with it back in the late '90s ). I've found out about (but don't know how to use properly) implicit parameters, linear implicit parameters, unsafePerformIO, "safe and sound implementation of polymorphic heap with references and updates (Oleg Kiselyov, (http://www.haskell.org/pipermail/haskell/2003-June/011939.html), implicit configurations, phantom types, ... I've also found warnings against many of these. I'm inclined to try the unsafePerformIO route as being the simplest, and most commonly used, even though perhaps the least haskell-ish. I like implicit configurations, but couldn't begin to say I understand them yet, and it's a bit heavy for a novice. In a nutshell: I want to use the old value of a tag to compute the new value, in a callback, I want to access the tag from other callbacks, and I want to the value to a mutable list from within the callback. I'd certainly be interested in doing without global variables, and would appreciate any advice. (By the way, I'm using Linux, and so far it looks like HTk is my choice for the GUI interface.) Best, John Velman
On Mon, 2004-11-22 at 23:34, John Velman wrote:
In a nutshell:
I want to use the old value of a tag to compute the new value, in a callback,
I want to access the tag from other callbacks, and
I want to the value to a mutable list from within the callback.
I'd certainly be interested in doing without global variables, and would appreciate any advice.
For GUI programming you don't need global variables. You can partially apply all those values to the callback that are necessary. In particular, those values can be MVars or IORefs which are like pointers to a value (i.e. you can modify them). For example to draw a bit of graphics: canvas <- drawingAreaNew text <- canvas `widgetCreateLayout` "Hello World." canvas `onExpose` updateCanvas canvas text where the function updateCanvas takes 3 arguments: updateCanvas :: DrawingArea -> PangoLayout -> Event -> IO Bool updateCanvas canvas text (Expose { area=rect }) = do
(By the way, I'm using Linux, and so far it looks like HTk is my choice for the GUI interface.)
I don't know if HTk is still maintained. The most popular GUI toolkit is wxHaskell now; if you're only developing on Unix then gtk2hs might be a choice. Axel.
On Monday 22 Nov 2004 6:27 pm, Lennart Augustsson wrote:
Personally, I can't believe I hear people arguing for global variables.
Oh dear, here we go again. I repeat, AFAIK nobody who wants a solution to this problem is advocating the use of "global variables", though it's true that the proposal under discussion would enable their creation if folk chose to be that foolish. For some reason it seems to have been left entirely up to me alone to defend the case for *top level* (not global!) mutable data structures. But I know I'm not the only one who wants a solution of some kind. Off the top of my head I can think of many others who've expressed the same desire at one time or another. I won't name names because I might be misrepresenting their views, but if you think they're all incompetent lazy Haskell programmers eager too shoot themselves in the foot because they just don't understand monadic IO, then you should think again. As for me, I strongly object to having any further consideration of this problem or the proposed solutions being kicked into the long grass by ill-considered "knee jerk" reactions of horror (or ridicule) concerning "global variables".
As for openDevice, if a device should only allow a single open I would assume this is part of the device driver in the operating system? (I know this is shifting blame. But I think it shifts it to where it belongs. In the OS there will be an "open" flag per device.)
IOW there is no possible sound solution in Haskell. I think that's a problem for a "general purpose" programming language. What if there is no OS or device driver? Shouldn't people reasonably expect to be able to write their own device driver in a general purpose programming language? Regards -- Adrian Hey
Is this a joke? Seriously if you writing the OS in haskell this is trivial, you fork a thread using forkIO at system boot to maintain the driver, all 'processes' communicate to the thread using channels, the thread maintains local state (an IORef, or just a peramiter used recursively) myDriver :: (Chan in,Chan out) -> State -> IO State myDriver (in,out) state = do -- read commands from in -- process commands -- reply on out myDriver (in,out) new_state Keean. Adrian Hey wrote:
IOW there is no possible sound solution in Haskell. I think that's
a problem for a "general purpose" programming language. What if there is no OS or device driver? Shouldn't people reasonably expect to be able to write their own device driver in a general purpose programming language?
Regards -- Adrian Hey
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Tuesday 23 Nov 2004 9:29 am, Keean Schupke wrote:
Is this a joke?
No.
Seriously if you writing the OS in haskell this is trivial, you fork a thread using forkIO at system boot to maintain the driver, all 'processes' communicate to the thread using channels, the thread maintains local state (an IORef, or just a peramiter used recursively)
myDriver :: (Chan in,Chan out) -> State -> IO State myDriver (in,out) state = do -- read commands from in -- process commands -- reply on out myDriver (in,out) new_state
How does this solve the problem we're talking about (namely preventing the accidental creation of multiple processes all of which believe they are "the" device driver for a particular unique resource)? I take it we can't expose myDriver to the world at large, so what the world at large sees must be just the unique channels to communicate with one myDriver (which is forked only once somewhere outside main). I can think of three ways of allowing the world at large to see the channels. 1- Have them as top level TWI's. I guess you're not in favour of that. 2- Have getChannels :: IO (Chan in,Chan out) instead. But this buys you no extra safety, and there's still the problem of how to implement getChannels if we're not allowed top level TWI's. 3- Have the in and out channels of this and every other periheral passed as an explicit argument to the user main. Yuk!, highly unmodular IMO, not mention having the type of main depend on what devices were available. Again it would seem an appropriate implementation of getChannels would be a top level .. getChannels <- oneShot $ do inChan <- newChan outChan <- newChan forkIO $ myDriver (inChan,outChan) state0 return (inChan,outChan) But of course this is so evil it's not worth further consideration :-) Regards -- Adrian Hey
Adrian Hey wrote:
On Tuesday 23 Nov 2004 9:29 am, Keean Schupke wrote:
myDriver :: (Chan in,Chan out) -> State -> IO State myDriver (in,out) state = do -- read commands from in -- process commands -- reply on out myDriver (in,out) new_state
How does this solve the problem we're talking about (namely preventing the accidental creation of multiple processes all of which believe they are "the" device driver for a particular unique resource)?
So do you agree with me that the protection against two drivers "opening" the same device does not belong in the driver code? (Because if it sits there I could mistakenly have another driver open the same device.) -- Lennart
There is no problem getting multiple copies of the channels... I take it you are not familiar with the internals of OSs (I have written a small OS myself, complete with real device drivers)... The OS is started at boot, it initialises its own state, then it forks the device drivers, then it forks user processes (simplified but adequate). Lets design a small Haskell OS, the OS has the handles for the device driver. The program MUST be passed the channels to the OS (there is no other way)... These channels allow other channels to be opened, they would be like the master device. main :: Chan CMD -> Chan RSP -> IO () main cmd rsp = do writeChan cmd (OpenDevice "devname") h <- readChan rsp case h of (OpenOK in out) -> do writeCan out (DeviceWriteString "hello") status <- readChan in _ -> error "could not open device" Here you can see that we could try and open the device again, however the OS would either multiplex or serialize the device depending on type. Keean. Adrian Hey wrote:
On Tuesday 23 Nov 2004 9:29 am, Keean Schupke wrote:
Is this a joke?
No.
Seriously if you writing the OS in haskell this is trivial, you fork a thread using forkIO at system boot to maintain the driver, all 'processes' communicate to the thread using channels, the thread maintains local state (an IORef, or just a peramiter used recursively)
myDriver :: (Chan in,Chan out) -> State -> IO State myDriver (in,out) state = do -- read commands from in -- process commands -- reply on out myDriver (in,out) new_state
How does this solve the problem we're talking about (namely preventing the accidental creation of multiple processes all of which believe they are "the" device driver for a particular unique resource)?
I take it we can't expose myDriver to the world at large, so what the world at large sees must be just the unique channels to communicate with one myDriver (which is forked only once somewhere outside main). I can think of three ways of allowing the world at large to see the channels.
1- Have them as top level TWI's. I guess you're not in favour of that. 2- Have getChannels :: IO (Chan in,Chan out) instead. But this buys you no extra safety, and there's still the problem of how to implement getChannels if we're not allowed top level TWI's. 3- Have the in and out channels of this and every other periheral passed as an explicit argument to the user main. Yuk!, highly unmodular IMO, not mention having the type of main depend on what devices were available.
Again it would seem an appropriate implementation of getChannels would be a top level ..
getChannels <- oneShot $ do inChan <- newChan outChan <- newChan forkIO $ myDriver (inChan,outChan) state0 return (inChan,outChan)
But of course this is so evil it's not worth further consideration :-)
Regards -- Adrian Hey
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Wednesday 24 Nov 2004 11:50 am, Keean Schupke wrote:
There is no problem getting multiple copies of the channels... I take it you are not familiar with the internals of OSs
IME there is no uniformity wrt OS internals, and I can't claim to be familiar with them all. It's also fairly safe to assume that I know nothing about the internals of your OS. In any case, this is irrelevant to the scenario I originally posited. Remember I wrote:
What if there is no OS or device driver? -------------------^^^^^
I.E. A typicial embedded environment (though it's common to use OSs here too, but the main reason for that is inadequacy of C). So all you have to work with is one complete, type safe, Haskell program, and "the metal". In this scenario the only initialisation that's done prior to running main is initialisation of the Haskell rts. That said, the approach you outline below is workable and AFAICS immune to the problems I was talking about. But you've introduced an artificial distinction between OS and application to do this and as a result.. * Made comms between application and hardware really awkward IMO * Sacrificed type safety in these comms I think.
(I have written a small OS myself, complete with real device drivers)... The OS is started at boot, it initialises its own state, then it forks the device drivers, then it forks user processes (simplified but adequate).
Lets design a small Haskell OS, the OS has the handles for the device driver. The program MUST be passed the channels to the OS (there is no other way)... These channels allow other channels to be opened, they would be like the master device.
main :: Chan CMD -> Chan RSP -> IO () main cmd rsp = do writeChan cmd (OpenDevice "devname") h <- readChan rsp case h of (OpenOK in out) -> do writeCan out (DeviceWriteString "hello") status <- readChan in _ -> error "could not open device"
Here you can see that we could try and open the device again, however the OS would either multiplex or serialize the device depending on type.
So you've gone for the third approach I identified, but with a slight variation. You've wrapped all the separate device drivers into a single uber device driver (world driver?) and called it the "operating system". I think this approach has it's pros and cons, but you're right that it does solve the problem. But my original monosyllabic summary of MHO re. this approach still applies I'm afraid. Regards -- Adrian Hey
Adrian Hey wrote:
As for openDevice, if a device should only allow a single open I would
assume this is part of the device driver in the operating system? (I know this is shifting blame. But I think it shifts it to where it belongs. In the OS there will be an "open" flag per device.)
IOW there is no possible sound solution in Haskell. I think that's a problem for a "general purpose" programming language. What if there is no OS or device driver? Shouldn't people reasonably expect to be able to write their own device driver in a general purpose programming language?
I find it hard to argue these things in the abstract. Could you post us a (simplified) signature for a module where you are using top level variables? Maybe that way I can be convinced that you need them. Or vice versa. :) If there's no OS nor driver you are free to do what you like, so I claim you can do without top level variables. I've written plenty of device drivers in C for NetBSD. They (almost) never use top level mutable variables (except to control debugging level). If you use top level variables it always bites you in the end. On some occasions I started with using top level mutables (like keeping a free list of transfer descriptors), but in the end I always had to change them to be local to some other piece of state. (I didn't change because of purity reasons, but out of necessity.) So my aversion for top level mutables does not stem from Haskell alone. -- Lennart
On Tuesday 23 Nov 2004 9:39 am, Lennart Augustsson wrote:
I find it hard to argue these things in the abstract. Could you post us a (simplified) signature for a module where you are using top level variables? Maybe that way I can be convinced that you need them. Or vice versa. :)
Nope, sorry, been down this route once before and I'm sick of these arguments. Fortunately (having just had time for a quick scan of John Meachams post) it seems JM has done an excellent job of this already. (So argue with him, I'm taking the day off :-) Regards -- Adrian Hey
Adrian Hey wrote:
On Tuesday 23 Nov 2004 9:39 am, Lennart Augustsson wrote:
I find it hard to argue these things in the abstract. Could you post us a (simplified) signature for a module where you are using top level variables? Maybe that way I can be convinced that you need them. Or vice versa. :)
Nope, sorry, been down this route once before and I'm sick of these arguments. Fortunately (having just had time for a quick scan of John Meachams post) it seems JM has done an excellent job of this already. (So argue with him, I'm taking the day off :-)
Enjoy your day off! I guess we will both remain unconvinced. :) -- Lennart
On Tuesday 23 November 2004 09:10, Adrian Hey wrote:
On Monday 22 Nov 2004 6:27 pm, Lennart Augustsson wrote:
Personally, I can't believe I hear people arguing for global variables.
Oh dear, here we go again. I repeat, AFAIK nobody who wants a solution to this problem is advocating the use of "global variables"
I don't understand the difference between a "global variable" (C code, outside main): int var = 0; and a "top-level thing with identity" (proposed Haskell code, outside main): var <- newIORef 0 AFAIK, "global" in C (or any other imperative language) means the same as "top-level" in Haskell. Ben -- Ceterum censeo: Global variabes are evil.
George Russel wrote:
Graham Klyne wrote (snipped):
I like the principle of parameterizing Show to allow for different encoding environments...
I like the idea too, not just for Show but for any instances. It seems to me that in general you should be able to combine the convenience of the Haskell type system with the power of Standard ML's structures and functors.
That can be -- and has been -- done: http://www.haskell.org/pipermail/haskell/2004-August/014463.html The running example includes an ORD class -- which is like the Ord class but can be parameterized by a comparison function, so to speak. That is, there may be several ORD instances for one type -- e.g., several ways to compare integers. Also, we do not need to carry the discriminating label or dictionary all the time. Here's a snippet from the test in the above article:
let set1_empty = inst fs LR (undefined::Int) s1 = add 1 (add 0 set1_empty) rm1 = member 2 s1
set3_empty = inst fs LE (undefined::Int) s3 = add 1 (add 0 set3_empty) r3 = member 2 s3
we parameterize the applicative, translucent functor ORD->SET with two different integer-comparison functions, represented by labels LR and LE. More meaningful names are certainly possible. After we instantiated the functor, we do not need to mention the ORD parameter ever again. Ken Shan's paper, the above and the following messages http://www.haskell.org/pipermail/haskell/2004-September/014515.html argue that Haskell already has the full power of Standard ML's structures and functors (and not only generative but applicative functors as well).
oleg@pobox.com wrote (snipped):
The running example includes an ORD class -- which is like the Ord class but can be parameterized by a comparison function, so to speak.
This is precisely the problem. Rather than being able to use the existing functions, you have to haul around an extra discriminator value. You cannot use for example Data.Set, instead you have to use a more complicated class which knows about this discriminator value. I find this especially frustrating because Data.Set already hauls around a dictionary telling it how to order things, and it is annoying that because we can't manipulate this directly we have to add extra data.
Ken Shan's paper, the above and the following messages http://www.haskell.org/pipermail/haskell/2004-September/014515.html
argue that Haskell already has the full power of Standard ML's structures and functors (and not only generative but applicative functors as well).
So does a Turing machine for that matter. The fact that you can translate Standard ML's functors into rather cumbersome Haskell proves nothing.
George Russell <ger@informatik.uni-bremen.de> wrote in article <4199E0D1.3000206@informatik.uni-bremen.de> in gmane.comp.lang.haskell.general:
Ken Shan's paper, the above and the following messages http://www.haskell.org/pipermail/haskell/2004-September/014515.html argue that Haskell already has the full power of Standard ML's structures and functors (and not only generative but applicative functors as well). So does a Turing machine for that matter. The fact that you can translate Standard ML's functors into rather cumbersome Haskell proves nothing.
A quick note on this point: the translation I present in that paper preserves type abstraction, in the sense that you won't be able to distinguish between an encoded complex number stored as Cartesian coordinates and an encoded complex number stored as polar coordinates. Or so I claim (; just to get across the intuitive difference between a translation into Turing machine code and my translation into System F-omega. But more importantly, I don't think the outcome of the translation is necessarily "rather cumbersome Haskell". Mark Jones's tutorial "Functional Programming with Overloading and Higher-Order Polymorphism" shows many elegant programming examples that would be written in ML with functors, and whose higher-kinded types fall within the range of (in other words, are explained by) my translation. As you suggest, we should try to bridge Haskell's high-power type and type-class system with ML's module system, quite possibly to mutual benefit, but dynamic scoping may not be part of the bridge. For example, perhaps what you are getting at is some kind of "import" or "open" mechanism to select a type-class instance, but of course I am unclear on the details. -- Edit this signature at http://www.digitas.harvard.edu/cgi-bin/ken/sig A Bush re-election would galvanise the opposition http://guardian.co.uk/print/0,3858,5054048-114515,00.html Nato is a threat to Europe and must be disbanded http://guardian.co.uk/print/0,3858,5057388-111202,00.html
participants (19)
-
Aaron Denney -
Adrian Hey -
Axel Simon -
Ben Rudiak-Gould -
Benjamin Franksen -
Chung-chieh Shan -
dpt@lotus.bostoncoop.net -
George Russell -
John Meacham -
John Velman -
Josef Svenningsson -
Judah Jacobson -
Keean Schupke -
Keith Wansbrough -
Lennart Augustsson -
Marcin 'Qrczak' Kowalczyk -
oleg@pobox.com -
Ralf Laemmel -
Tomasz Zielonka