Hello Ertugrul,
Post by Ertugrul SöylemezPerhaps it's just me, but I've never used the unsafePerformIO hack.
Have you ever written an IO lib? A non-trivial FFI binding perhaps?
I'm not saying that even if you have you would necessarily have needed
to use the unsafePerformIO hack. Sometimes you don't need it, but
often use of a top level MVar or something (to implement a lock
say) really is the *only* semantically correct and safe solution,
unless you're going to just say the lib is not thread safe and
should not be used in concurrent apps (a pretty severe limitation
IMO). Unfortunately Haskell provides no safe way to do this.
Even some of those that have managed to avoid it only do so by
presenting an inherently unsafe API (e.g. SDL)
BTW, anyone who has ever used stdout has used a "global variable"
created using the unsafePerformIO hack. Or at least ghc users, take a
look at the GHC.Handle source. Funny thing is, hardly anyone even
recognises stdout,stdin,stderr as "global variables". Or they do, but
for some mysterious reason (never explained) these and *only* these are
not "evil".
Post by Ertugrul SöylemezIt has been shown that a complete operating system can be implemented
entirely in Haskell. Have a look at House [1].
[1] http://programatica.cs.pdx.edu/House/
You mean the "Principled Approach to Operating System Construction in
Haskell"? :-)
Again, perhaps you should take a look at the source. Even for this
modest OS I can count at least 8 uses of the "unsafePerformH hack" to
create "global variables". There would be even more if I included the
cbits of this entirely written in Haskell OS. Not that I would ever be
one to suggest that this is in any way unprincipled.
:-)
Some conclusions re. the suitablility of Haskell in it's current form
for such projects can be found in these papers:
http://web.cecs.pdx.edu/~rebekah/papers/icfp05_h.pdf
http://web.cecs.pdx.edu/~rebekah/papers/plos07.pdf
No real surprises there, but strangely neither of these papers mentions
the dependence on "global variables" nor the reliance on an unsound hack
to create them. I would have thought a minor problem like not even being
able to rely on correct compilation might have been worth a mention in
passing :-)
It all adds to my suspicion that there is a real conspiracy of silence
about this problem in the research community, like this is the problem
that nobody wants (dares) to mention or admit exists. The silence from
ghchq on this issue is also "deafening" (as they say). I really can't
imagine why that might be.
Post by Ertugrul SöylemezOf course as well I might be misunderstanding your idea of modularity.
I mean that without top level state (a lock say) I have to provide a
library API that is not only less excessively complex and less
convenient to use and maintain, it is also less safe.
For the lock example, instead of this..
module LockDemo (doSomething) where
lock :: MVar ()
lock <- newMVar () -- Hypothetical top level <- binding
doSomething :: IO Whatever
doSomething = do ...
takeMVar lock
<call some dodgy C>
<call some more dodgy C>
putMVar lock ()
...
...I have to use something like this..
module LockDemo (Lock,newLock,doSomething) where
newtype Lock = Lock (MVar ())
-- | If you make more than one of these you're screwed
newLock :: IO Lock
newLock = do mvar <- newMVar ()
return (Lock mvar)
doSomething :: Lock -> IO Whatever
doSomething (Lock lock) = do ...
takeMVar lock
<call some dodgy C>
<call some more dodgy C>
putMVar lock ()
...
I have now have no static guarantee that only one lock will be created.
I have no alternative but to "beg" users for safe use via a Haddock
comment. As any libs that may interface to me cannot assume they
have a monopoly on my services they must in turn "pass the buck" of
uniqueness responsibility on in their own APIs, and so on..until we
end up at main itself. Not exactly modular IMO.
Furthermore, assuming everybody has respected the uniquiness properties,
I still have to ensure that doSomething gets the correct lock passed. So
if I have several locks, the only safe way to do this is to make them
distinct types.
Other libs might well be modified to make use of my services too at some
point, but they can't do this without changing their own APIs because
they need to take the Lock(s) as arguments. Also, suppose the C became
less dodgy in a later revison. It would be nice to eliminate the locks,
but now I can't do that without changing the API (or at least there's
little point in doing this if I'm not going to simplify the API). So
it's not exactly maintainable either IMO.
And no, I don't think exotic library or application level monads help
solve this problem :-(
And no again (before anybody says it), the problem is not with "badly
designed" C libs. Many of them certainly are, but if you stripped away
all the "badly designed" C libs and "badly designed" OS interfaces
you'd just end up interfacing directly to "badly designed" hardware.
Haskell should be able to safely interface to any "badly designed"
environment at whatever system level, or else depend only on "well
designed" interfaces that could never actually be implemented in Haskell
(not the characteristics of a general purpose programming language).
Post by Ertugrul SöylemezThere is something wrong with global variables in the Haskell paradigm,
because since everything is pure, a global modifyable variable isn't
even expressable. If you really need them, either you use StateT, which
is perfectly safe, pure and modular (remember that multiple StateTs can
be combined) or you use the ugly variant of essentially the same, an
unsafePerformIO hack.
Sorry I don't really know how to answer this, but it looks to me as if
you don't understand the problem people are trying to solve when they
use the unsafePerformIO hack. If so, you would certainly not be the
first to misunderstand this which is why it is so hard to have a
sensible discussion about this problem (people have already made up
their minds about the "right way to do it" before they've even taken
the time to understand the problem). I don't know if my words above have
clarified this at all :-)
Remember the concurrency semantics of MVars, Chans and functions like
atomicModifyIORef? I don't think StateT is any substitute for a genuine
MVar or IORef.
Post by Ertugrul SöylemezFor some reason, people refrain from using StateT here, but prefer to
use unsafePerformIO.
I think they use they unsafePerformIO hack because it is the only way
to get a unique MVar or whatever at the top level and that is what they
need :-)
Post by Ertugrul SöylemezHonestly I've never tried JHC. I'll give it a shot, when I've got some
time. =)
Well I don't use it myself either. I think at the moment it's very much
work in progress, so I don't know if it really is realistically useable
(yet).
Regards
--
Adrian Hey