Re: [Haskell] Re: Global Variables and IO initializers
You don't want stdin/stdout/stderr?
Also these are only available in the IO monad...
without breaking referential transparency by use of unsafePerformIO hack.
I don't understand this still... how can it not break referntial transparancy. For example consider if stdin were available outside the IO monad. Could someone give an example of what these things are that need to be initialised and that are safe. The reason these things are unsafe is if I have two references to the same thing, and one reference is used to mutate the thing, the other will see the mutated value and not the original value as it should. The only solution has to be these things must be constants or in the ST or IO monad. Keean.
On Fri, Nov 05, 2004 at 07:03:06PM +0000, MR K P SCHUPKE wrote:
You don't want stdin/stdout/stderr?
Also these are only available in the IO monad...
No, they are available "outside" the IO monad, only you can't do anything useful with them. Well, you can show them!
without breaking referential transparency by use of unsafePerformIO hack.
I don't understand this still... how can it not break referntial transparancy. For example consider if stdin were available outside the IO monad.
If you could do interesting things with Handles "outside" the IO monad, we would be doomed anyway. Best regards, Tomasz -- .signature: Too many levels of symbolic links
On 5 Nov 2004, at 19:03, MR K P SCHUPKE wrote:
You don't want stdin/stdout/stderr?
Also these are only available in the IO monad...
without breaking referential transparency by use of unsafePerformIO hack.
I don't understand this still... how can it not break referntial transparancy. For example consider if stdin were available outside the IO monad.
Could someone give an example of what these things are that need to be initialised and that are safe.
The typical example is an IORef. As in: myRef :: IORef Int myRef = unsafePerformIO $ newIORef 0 Now it's perfectly safe for myRef to exist outside the IO monad: all it is, is some kind of pointer to the actual value. Of course, we can't do anything to it outside the IO monad, but it can exist. Therefore, if it is at the toplevel, it can be referenced by all the other functions in the same module. The functions therefore share a common 'pointer' to a value: however none of them can actually access the value or change it without using the IO monad, as is right and proper. This is typical of the kind of commutative operations that Koen proposes: what it is really about is 'fresh names'. newIORef returns a 'fresh name' for a 'box' which stores things. (I notice that name supply is one of the key issues for John Petersen too). There appears to be no problem in principle with having such objects around at the top level, but haskell provides no syntax to create them (safely). The syntax above is obviously 'wrong' in that it is rather assuming that two different calls to unsafePerformIO $ newIORef 0 will return different values, which violates ref. integrity. The compiler has something like an internal fresh name monad: every time you define a new top-level value, it is given some fresh (internal) name. Most language compilers can be thought of as operating like this, but haskell makes it hard to get at the bit of this monad we want. Jules
Okay, now for the purposes of my understanding, let me explore this: myRef :: IORef Int myRef = unsafePerformIO $ newIORef 0 This should always return the same reference, whereas: myIORef :: IO (IORef Int) myIORef = newIORef 0 Will return a new reference every time. I agree it would seem that the first form does not need to be in the IO monad as it is effectively a constant. The problem I guess is there is no way in the language itself to indicate that the function should not be inlined (which will make each use of myRef refer to a different IORef). So what we need is a way in the type system to tell the compiler the function must have a single unique definition... Something like: myRef :: Unique (IORef Int) myRef = uniquePerformIO $ newIORef 0 and then have: runUnique :: Unique x -> x Then modify the compilers to never inline fundtions in the Unique monad. Is this what the CIO Monad proposes? Keean Jules Bean wrote:
On 5 Nov 2004, at 19:03, MR K P SCHUPKE wrote:
You don't want stdin/stdout/stderr?
Also these are only available in the IO monad...
without breaking referential transparency by use of unsafePerformIO hack.
I don't understand this still... how can it not break referntial transparancy. For example consider if stdin were available outside the IO monad.
Could someone give an example of what these things are that need to be initialised and that are safe.
The typical example is an IORef.
As in:
myRef :: IORef Int myRef = unsafePerformIO $ newIORef 0
Now it's perfectly safe for myRef to exist outside the IO monad: all it is, is some kind of pointer to the actual value. Of course, we can't do anything to it outside the IO monad, but it can exist.
Therefore, if it is at the toplevel, it can be referenced by all the other functions in the same module. The functions therefore share a common 'pointer' to a value: however none of them can actually access the value or change it without using the IO monad, as is right and proper.
This is typical of the kind of commutative operations that Koen proposes: what it is really about is 'fresh names'. newIORef returns a 'fresh name' for a 'box' which stores things. (I notice that name supply is one of the key issues for John Petersen too).
There appears to be no problem in principle with having such objects around at the top level, but haskell provides no syntax to create them (safely). The syntax above is obviously 'wrong' in that it is rather assuming that two different calls to unsafePerformIO $ newIORef 0 will return different values, which violates ref. integrity.
The compiler has something like an internal fresh name monad: every time you define a new top-level value, it is given some fresh (internal) name. Most language compilers can be thought of as operating like this, but haskell makes it hard to get at the bit of this monad we want.
Jules
On Friday 05 November 2004 22:07, Keean Schupke wrote:
So what we need is a way in the type system to tell the compiler the function must have a single unique definition... Something like:
myRef :: Unique (IORef Int) myRef = uniquePerformIO $ newIORef 0
and then have:
runUnique :: Unique x -> x
In Eiffel it is called 'once' istead of 'Unique', e.g. (excuse my rusty Eiffel, the syntax may be wrong) class XYZ feature once ref : Int do ...routine body here... Result := ... end end The semantics is that the routine body is executed at most once, namely when the feature is used for the first time. Note that Eiffel allows arbitrary IO actions to be performed in the body of once routines, just like in your Haskell example above. It is interesting to note that the Eiffel community is quite aware of the problems this solution has, i.e. that the procedure may have side-effects that happen at some unpredictable moment in time -- especially when concurrent execution comes into play. It is regarded as a matter of programmer discipline to ensure that once routines do not have effects visible outside the class in which they are defined. Such an appeal to programmer discipline clearly fits not well with the spirit of Haskell. I would argue that the actions to be performed inside such a 'once' or 'unique' initialization must be strictly limited to harmless ones like allocation of reference cells. As i pointed out earlier, elements of a commutative sub-monad of IO are not automatically harmless. How else can we define "harmless" IO actions? Maybe Ben Rudiak-Gould's idea to use (forall s . ST s) is teh right idea but I still don't understand it... Ben
On Friday 05 November 2004 22:07, Keean Schupke wrote:
myRef :: IORef Int myRef = unsafePerformIO $ newIORef 0
This should always return the same reference, whereas:
myIORef :: IO (IORef Int) myIORef = newIORef 0
Will return a new reference every time. I agree it would seem that the first form does not need to be in the IO monad as it is effectively a constant.
Yes, but I guess everybody would like a solution where myRef1 = unsafePerformIO $ newIORef 0 myRef2 = unsafePerformIO $ newIORef 0 are different variables. Also, it's not true that it's perfectly safe, we are just assuming here that all the global values are evaluated before the main action, which should not be so obvious, if main is executed before the two unsafePerformIO actions we will get undefined behaviour, that's why newIORef has an "IO" type after all. V.
Vincenzo Ciancia wrote:
Yes, but I guess everybody would like a solution where
myRef1 = unsafePerformIO $ newIORef 0 myRef2 = unsafePerformIO $ newIORef 0
are different variables. Also, it's not true that it's perfectly safe,
I don't understant this - they would be different variables with Haskell as it stands now, and they would be different variables using the unique monad (or equivalent) - You are right that it is not perfectly safe, but only because the compiler might inline the definition. Each inlined version of the function would then refer to a different IORef. With GHC you can use the noinline pragma to tell the compiler not to inline the function - which makes it completely safe, but compiler pragma's are implementation specific and not a satisfactory solution. So you can see what is needed is a way to tell the compiler not to inline the function that is part of the language. Its tempting to define a keyword, but this again is unsatifactory - as we would like to limit proliferation of keywords... The solution seems to be to use the type-system, and intoduce a type that implies functions returning this type should not be inlined. Keean
Just been reading arround. According to ghc docs, the noinline pragma is in the Haskell98 report. On that basis what is wrong with using the following to initialise these top-level constants? {-# NOINLINE newref #-} newref :: IORef Int newref = unsafePerformIO $ newIORef 0 Keean.
On 6 Nov 2004, at 13:07, Keean Schupke wrote:
Just been reading arround. According to ghc docs, the noinline pragma is in the Haskell98 report. On that basis what is wrong with using the following to initialise these top-level constants?
{-# NOINLINE newref #-} newref :: IORef Int newref = unsafePerformIO $ newIORef 0
What's wrong is that it isn't haskell. (NOINLINE may be in the haskell report, but unsafePerformIO isn't.. and without unsafe operations, NOINLINE is of course quite safe)
myRef1 = unsafePerformIO $ newIORef 0 myRef2 = unsafePerformIO $ newIORef 0
In a referentially transparent language, these two values myRef1 and myRef2 *must* be the same. That's the whole thrust of referential transparency. But it is certainly not what the user intends.... Jules
On Saturday 06 Nov 2004 1:07 pm, Keean Schupke wrote:
Just been reading arround. According to ghc docs, the noinline pragma is in the Haskell98 report. On that basis what is wrong with using the following to initialise these top-level constants?
{-# NOINLINE newref #-} newref :: IORef Int newref = unsafePerformIO $ newIORef 0
1- It's awkward, and only safe if programmer actually remembers to use NOINLINE (compiler won't throw out an error if it's ommited). 2- It's ugly. Arguably this is mere aesthetics, but if it really is perfectly safe it should not be necessary to mention the word "unsafe" anywhere. With this solution programmers still have to "put their thinking caps on" to figure out if it's safe. It would be better if the language design and type system guaranteed it was safe. 3- According to current ghc docs, you still have to compile the module with the -fno-cse flag too. This may or may not be visible in the source code (with OPTIONS pragma). If it isn't the programmer has to look elsewhere (in makefiles or whatever) to check this flag has been used. 4- -fno-cse applies to an entire module, which will be overkill in most cases. Regards -- Adrian Hey
On Friday 05 Nov 2004 7:03 pm, MR K P SCHUPKE wrote:
Could someone give an example of what these things are that need to be initialised and that are safe.
Here's a utility I've concocted for dealing with partial ordering constraints on initialisation of foreign libraries.. oneShot :: IO a -> IO (IO a) oneShot io = mdo mv <- newMVar $ do a <- io let loop = do putMVar mv loop return a loop return $ do act <- takeMVar mv act The idea being that oneShot takes a real initialising action as argument and returns a new action which will perform the real initialisation at most once, no matter how many times it's used. Suppose I want to use this to create a userInit (which is exported) from a realInit (which isn't exported). Currently I have to write.. userInit :: IO <whatever> userInit = unsafePerformIO $ oneShot realInit but I think what I would really like is something like this perhaps.. -- For use from SafeIO monad oneShotSafeIO :: IO a -> SafeIO (IO a) <same definition> -- For use from IO monad oneShotIO :: IO a -> IO (IO a) oneShotIO io = liftSafeIO $ oneShotSafeIO io userInit :: IO <whatever> userInit <- oneShotSafeIO realInit Though this could be simplified if SafeIO could be made a sub-type of IO I guess (but I don't know a way to do this). Regards -- Adrian Hey
The problem I see here is how to proove the IO in safeIO is indeed safe. Perhaps "UnsafeIO" is a better name, as infact the IO is still unsafe - the compiler has to take special notice of this type and not inline its definitions. Your oneShot function has the same problem - if the compiler inlines the funtion you get two 'oneShot' functions. Keean. Adrian Hey wrote:
On Friday 05 Nov 2004 7:03 pm, MR K P SCHUPKE wrote:
Could someone give an example of what these things are that need to be initialised and that are safe.
Here's a utility I've concocted for dealing with partial ordering constraints on initialisation of foreign libraries..
oneShot :: IO a -> IO (IO a) oneShot io = mdo mv <- newMVar $ do a <- io let loop = do putMVar mv loop return a loop return $ do act <- takeMVar mv act
The idea being that oneShot takes a real initialising action as argument and returns a new action which will perform the real initialisation at most once, no matter how many times it's used.
Suppose I want to use this to create a userInit (which is exported) from a realInit (which isn't exported).
Currently I have to write..
userInit :: IO <whatever> userInit = unsafePerformIO $ oneShot realInit
but I think what I would really like is something like this perhaps..
-- For use from SafeIO monad oneShotSafeIO :: IO a -> SafeIO (IO a) <same definition>
-- For use from IO monad oneShotIO :: IO a -> IO (IO a) oneShotIO io = liftSafeIO $ oneShotSafeIO io
userInit :: IO <whatever> userInit <- oneShotSafeIO realInit
Though this could be simplified if SafeIO could be made a sub-type of IO I guess (but I don't know a way to do this).
Regards -- Adrian Hey
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Inling isn't the only optimization, which can lead to a "wrong" behavior, "let floating out" and "common subexpression elimination" can also change the behavior of programs using unsafePerformIO. Our research group has developed the calculus FUNDIO as a semantic basis: It's a non-deterministic call-by-need lambda calculus with a contextual equivalence. Furthermore, with HasFuse there exists a modified implementation of the Glasgow Haskell Compiler which compiles Haskell programs using unsafePerformIO in a 'safe' way, i.e. deploys only those optimizations that have been proved correct w.r.t. FUNDIO. The technical report describing FUNDIO is available at http://www.ki.informatik.uni-frankfurt.de/papers/schauss/FUNDIO.pdf More information about the related research project "DIAMOND": http://www.ki.informatik.uni-frankfurt.de/research/diamond/en/ Cheers, David Keean Schupke wrote:
The problem I see here is how to proove the IO in safeIO is indeed safe. Perhaps "UnsafeIO" is a better name, as infact the IO is still unsafe - the compiler has to take special notice of this type and not inline its definitions.
Your oneShot function has the same problem - if the compiler inlines the funtion you get two 'oneShot' functions.
Keean.
Adrian Hey wrote:
On Friday 05 Nov 2004 7:03 pm, MR K P SCHUPKE wrote:
Could someone give an example of what these things are that need to be initialised and that are safe.
Here's a utility I've concocted for dealing with partial ordering constraints on initialisation of foreign libraries..
oneShot :: IO a -> IO (IO a) oneShot io = mdo mv <- newMVar $ do a <- io let loop = do putMVar mv loop return a loop return $ do act <- takeMVar mv act
The idea being that oneShot takes a real initialising action as argument and returns a new action which will perform the real initialisation at most once, no matter how many times it's used.
Suppose I want to use this to create a userInit (which is exported) from a realInit (which isn't exported).
Currently I have to write..
userInit :: IO <whatever> userInit = unsafePerformIO $ oneShot realInit
but I think what I would really like is something like this perhaps..
-- For use from SafeIO monad oneShotSafeIO :: IO a -> SafeIO (IO a) <same definition>
-- For use from IO monad oneShotIO :: IO a -> IO (IO a) oneShotIO io = liftSafeIO $ oneShotSafeIO io
userInit :: IO <whatever> userInit <- oneShotSafeIO realInit
Though this could be simplified if SafeIO could be made a sub-type of IO I guess (but I don't know a way to do this).
Regards -- Adrian Hey
_______________________________________________ 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
I hope this is not a stupid idea - but why not contribute the changes as patches back to the main GHC development? Keean. David Sabel wrote:
Inling isn't the only optimization, which can lead to a "wrong" behavior, "let floating out" and "common subexpression elimination" can also change the behavior of programs using unsafePerformIO.
Our research group has developed the calculus FUNDIO as a semantic basis: It's a non-deterministic call-by-need lambda calculus with a contextual equivalence. Furthermore, with HasFuse there exists a modified implementation of the Glasgow Haskell Compiler which compiles Haskell programs using unsafePerformIO in a 'safe' way, i.e. deploys only those optimizations that have been proved correct w.r.t. FUNDIO.
The technical report describing FUNDIO is available at http://www.ki.informatik.uni-frankfurt.de/papers/schauss/FUNDIO.pdf
More information about the related research project "DIAMOND": http://www.ki.informatik.uni-frankfurt.de/research/diamond/en/
Cheers, David
Keean Schupke wrote:
The problem I see here is how to proove the IO in safeIO is indeed safe. Perhaps "UnsafeIO" is a better name, as infact the IO is still unsafe - the compiler has to take special notice of this type and not inline its definitions.
Your oneShot function has the same problem - if the compiler inlines the funtion you get two 'oneShot' functions.
Keean.
Adrian Hey wrote:
On Friday 05 Nov 2004 7:03 pm, MR K P SCHUPKE wrote:
Could someone give an example of what these things are that need to be initialised and that are safe.
Here's a utility I've concocted for dealing with partial ordering constraints on initialisation of foreign libraries..
oneShot :: IO a -> IO (IO a) oneShot io = mdo mv <- newMVar $ do a <- io let loop = do putMVar mv loop return a loop return $ do act <- takeMVar mv act
The idea being that oneShot takes a real initialising action as argument and returns a new action which will perform the real initialisation at most once, no matter how many times it's used.
Suppose I want to use this to create a userInit (which is exported) from a realInit (which isn't exported).
Currently I have to write..
userInit :: IO <whatever> userInit = unsafePerformIO $ oneShot realInit
but I think what I would really like is something like this perhaps..
-- For use from SafeIO monad oneShotSafeIO :: IO a -> SafeIO (IO a) <same definition>
-- For use from IO monad oneShotIO :: IO a -> IO (IO a) oneShotIO io = liftSafeIO $ oneShotSafeIO io
userInit :: IO <whatever> userInit <- oneShotSafeIO realInit
Though this could be simplified if SafeIO could be made a sub-type of IO I guess (but I don't know a way to do this).
Regards -- Adrian Hey
_______________________________________________ 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
The main reason is: Nobody asks for it. I conjecture, a problem is: if you use FUNDIO as a semantics for Haskell, you have to give up referential transparency in the strong sense. FUNDIO-programs are only referential transparent with respect to the defined contextual equivalence. David Keean Schupke wrote:
I hope this is not a stupid idea - but why not contribute the changes as patches back to the main GHC development?
Keean.
David Sabel wrote:
Inling isn't the only optimization, which can lead to a "wrong" behavior, "let floating out" and "common subexpression elimination" can also change the behavior of programs using unsafePerformIO.
Our research group has developed the calculus FUNDIO as a semantic basis: It's a non-deterministic call-by-need lambda calculus with a contextual equivalence. Furthermore, with HasFuse there exists a modified implementation of the Glasgow Haskell Compiler which compiles Haskell programs using unsafePerformIO in a 'safe' way, i.e. deploys only those optimizations that have been proved correct w.r.t. FUNDIO.
The technical report describing FUNDIO is available at http://www.ki.informatik.uni-frankfurt.de/papers/schauss/FUNDIO.pdf
More information about the related research project "DIAMOND": http://www.ki.informatik.uni-frankfurt.de/research/diamond/en/
Cheers, David
Keean Schupke wrote:
The problem I see here is how to proove the IO in safeIO is indeed safe. Perhaps "UnsafeIO" is a better name, as infact the IO is still unsafe - the compiler has to take special notice of this type and not inline its definitions.
Your oneShot function has the same problem - if the compiler inlines the funtion you get two 'oneShot' functions.
Keean.
Adrian Hey wrote:
On Friday 05 Nov 2004 7:03 pm, MR K P SCHUPKE wrote:
Could someone give an example of what these things are that need to be initialised and that are safe.
Here's a utility I've concocted for dealing with partial ordering constraints on initialisation of foreign libraries..
oneShot :: IO a -> IO (IO a) oneShot io = mdo mv <- newMVar $ do a <- io let loop = do putMVar mv loop return a loop return $ do act <- takeMVar mv act
The idea being that oneShot takes a real initialising action as argument and returns a new action which will perform the real initialisation at most once, no matter how many times it's used.
Suppose I want to use this to create a userInit (which is exported) from a realInit (which isn't exported).
Currently I have to write..
userInit :: IO <whatever> userInit = unsafePerformIO $ oneShot realInit
but I think what I would really like is something like this perhaps..
-- For use from SafeIO monad oneShotSafeIO :: IO a -> SafeIO (IO a) <same definition>
-- For use from IO monad oneShotIO :: IO a -> IO (IO a) oneShotIO io = liftSafeIO $ oneShotSafeIO io
userInit :: IO <whatever> userInit <- oneShotSafeIO realInit
Though this could be simplified if SafeIO could be made a sub-type of IO I guess (but I don't know a way to do this).
Regards -- Adrian Hey
_______________________________________________ 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
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
David Sabel wrote:
The main reason is: Nobody asks for it.
Actually I think Simon Marlow has talked in the past about wanting to make GHC only do safe optimisations on unsafePerformIO.
I conjecture, a problem is: if you use FUNDIO as a semantics for Haskell, you have to give up referential transparency in the strong sense. FUNDIO-programs are only referential transparent with respect to the defined contextual equivalence.
David
Surely all programs are only referentialy transparent with regards to the defined contextual equivalence? (just there is only one notion of equivalence used so far)... What would the problem be with intoducing other notions of equivalence to cope with things like unique naming... After all you would only change the definition of equality for unsafePerformIO and not for any other function. Keean.
Keean Schupke wrote:
David Sabel wrote:
The main reason is: Nobody asks for it.
Actually I think Simon Marlow has talked in the past about wanting to make GHC only do safe optimisations on unsafePerformIO.
I conjecture, a problem is: if you use FUNDIO as a semantics for Haskell, you have to give up referential transparency in the strong sense. FUNDIO-programs are only referential transparent with respect to the defined contextual equivalence.
David
Surely all programs are only referentialy transparent with regards to the defined contextual equivalence? (just there is only one notion of equivalence used so far)... What would the problem be with intoducing other notions of equivalence to cope with things like unique naming...
Perhaps something like this is possible, I don't know.
After all you would only change the definition of equality for unsafePerformIO and not for any other function.
There's a problem: Other functions can call functions which make use of unsafePerformIO. Maybe a dependency analysis could a solution to split the parts of the program into one part which is 'unsafe' and needs a special treatment and the other 'pure' part. David
Keean. _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On Saturday 06 Nov 2004 12:27 pm, Keean Schupke wrote:
The problem I see here is how to proove the IO in safeIO is indeed safe. Perhaps "UnsafeIO" is a better name, as infact the IO is still unsafe -
I don't agree. All top level bindings currently have the property that their value is independent of when they get reduced. If the only "IO" primitives supported by the hypothetical SafeIO monad are the creation of IORefs, MVars, etc how can this property be lost? Bear in mind that reading or writing IORefs, MVars etc is forbidden. Or is there some other essential property of top level bindings you have in mind? In this case we should really talk about "initial state" rather than "value" I guess, hence the identity problem which you allude to below..
the compiler has to take special notice of this type and not inline its definitions. Your oneShot function has the same problem - if the compiler inlines the funtion you get two 'oneShot' functions.
Of course. The whole point of the proposed <- syntax is that it provides an "official" and convenient way to tell the compiler _not_ to inline and also apply typing restrictions (and not to do some other optimisations too no doubt, such as CSE). Regards -- Adrian Hey
As an experiment, I just finished to change the Haskell Web Server with Plugins such that all global variables (unsafePerformIO-style) are replaced by standard argument passing. It wasn't difficult. The main work was (1) get it to compile with ghc-6.2.2 (2) understand how the code is organized (3) find out that implicit parameters have too many limitations to be usefull as a general replacement (4) find appropriate pattern(s) to get rid of the globals Overall I think the code has somewhat improved. The parts that were written with global variables are now shorter and more easily understood. What I didn't expect was that modularity did *not* suffer, quite the opposite: the interfaces became smaller. For instance the MimeTypes module exported two routines: initMimeTypes :: String -> IO () -- argument is file path to mime.conf mimeTypeOf :: String -> MimeType -- convert file path to mime type where unsafePerformIO was used not only to create the global variable for the mime type map, but also for the conversion function (because it had to access teh global var). The new interface has only one routine: initMimeTypes :: String -> IO (String -> MimeType) -- argument is file path to mime.conf -- result is file path to mimetype converter and no unsafe feature is used: the result is a pure function. Of course, the downside is that some of the functions (not many) now have one or two additional arguments. OTOH one could argue that this is in fact an advantage, as it makes all the dependencies crystal clear. It turned out, for example, that of the two logging modules, ErrorLogger and AccessLogger, the latter had a hidden dependency on the former. That dependency is now expressed explicitly by giving the initialization routine for the AccessLogger an extra argument (namely the error logging function). Surely this is just one example, and not a very complex one. Nevertheless, I am now less convinced that using global variables is in fact a good idea, however convenient it may seem at first. Ben
On Sunday 07 Nov 2004 3:16 am, Benjamin Franksen wrote:
Of course, the downside is that some of the functions (not many) now have one or two additional arguments. OTOH one could argue that this is in fact an advantage, as it makes all the dependencies crystal clear.
I wouldn't argue that :-)
Surely this is just one example, and not a very complex one. Nevertheless, I am now less convinced that using global variables is in fact a good idea, however convenient it may seem at first.
I'm not at all convinced, having not seen or groked either the "before" or "after" code. Perhaps you could show how this would work with an even simpler example, the one that I posted concerning the use of oneShot to create a top level (I.E. exportable) userInit. AFAICS the only alternative to.. userInit <- oneShot realInit is to export realInit, have users create their own userInit, and then pass that around as an argument to everything that might make use of userInit. Maybe I'm missing something, but this doesn't seem very attractive to me as a library writer (it means I must expose realInit and just trust users to only use it once). It doesn't seem very attractive to users either (considerably complicates their code and places the burden on them to "get it right"). Regards -- Adrian Hey
Adrian Hey wrote:
I'm not at all convinced, having not seen or groked either the "before" or "after" code. Perhaps you could show how this would work with an even simpler example, the one that I posted concerning the use of oneShot to create a top level (I.E. exportable) userInit.
AFAICS the only alternative to..
userInit <- oneShot realInit
is to export realInit, have users create their own userInit, and then pass that around as an argument to everything that might make use of userInit.
The way I would do it would be to have an init function that initialises an abstract data structure. Because the results of the init function are stateless and not in a global variable it does not matter if the user calls it twice. By not exporting the constructors for the data type from your module the 'user' will not be able to get at the contents. Keean
On Sunday 07 Nov 2004 3:18 pm, Keean Schupke wrote:
The way I would do it would be to have an init function that initialises an abstract data structure. Because the results of the init function are stateless and not in a global variable it does not matter if the user calls it twice.
I don't understand the relevance of this. In the example I gave we're not talking about an abstract data structure and the init function is not stateless. I can assure you that for the intended applications of oneShot it is vital that realInit is executed once at most, but the user must have the freedom to execute userInit as many times as they need (I.E. without the burden of having to keep track of whether or not they've used it before). So please, no more handwaving arguments about this kind of thing being unnecessary, bad programming style, or whatever.. Please show me a concrete alternative in real Haskell code, other than the IMO horrible alternative which I have already suggested. If that's the only alternative available I will continue to use the unsafePerformIO hack, I'm sorry to say :-( Regards -- Adrian Hey
participants (8)
-
Adrian Hey -
Benjamin Franksen -
David Sabel -
Jules Bean -
Keean Schupke -
MR K P SCHUPKE -
Tomasz Zielonka -
Vincenzo Ciancia