Open acfoltzer opened 12 years ago
I don't presently see a better solution than this, though certainly we'd like to scrap that boilerplate if we could.
What safety issue are you referring to here? The problem with returning IVars from a Par computation?
I had a suggestion from Nick Smallbone recently that appears to solve this problem in an relatively unintrusive way, but it's a bit delicate. The idea is to give runPar this type:
runPar :: Typeable a => Par a -> a
Now, with this type it seems to be impossible to use runPar in such a way that the type system is subverted. I challenge you to try :)
For this ticket, we are actually referring to safety in the Safe Haskell sense; with Par
exported as a MonadIO
instance, runPar
amounts to unsafePerformIO
. However, we want to keep the MonadIO
instance out there so that meta-scheduler resources can use it for implementation purposes, but still export a Safe Haskell interface to end users.
Regarding IVar escape, this seems to do the trick, no?
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE StandaloneDeriving #-}
import Control.Monad.Par.Class
import Control.Monad.Par.Meta.SMP
import Data.Typeable
deriving instance Typeable1 IVar
runPar' :: Typeable a => Par a -> a
runPar' = runPar
uhoh = let escapee :: IVar Int
escapee = runPar' $ do iv <- new :: Par (IVar Int)
fork (get iv >> return ())
return iv
in runPar' (put escapee 5)
This example currently blows up meta-par
, so it might be a good case for debugging. With a nested scheduler, it seems like this ought to work, even if it's semantically rather nonsensical.
I agree that a MonadIO instance is unsafe, but I'll take your word for it that you want it anyway...
Re your example, you can only cause a runtime crash if you can make a polymorphic IVar escape from a Par computation, because then you can write it at one type and read it at another. The Typeable constraint prevents that from happening. I believe the example you gave still has deterministic behaviour (although specifying what behaviour it actually has might be tricky!).
As to the question of WHY we want IO --
Basically we are finding scenarios where we do want to write effectful parallel programs, but would still prefer the lighter-weight monad-par scheduling to forkIO+MVars. For example, we are looking into implementing certain distributed web services using meta/monad-par.
My original idea for this is insufficient. I separated the unsafe liftIO equivalent into a module that is marked as unsafe:
Control.Monad.Par -- provides concrete API functions, Safe
Control.Monad.Par.Class -- provides overloaded API functions, Safe
Control.Monad.Par.Unsafe -- provides ParUnsafe class with liftIO equivalent, UNSAFE
This allows pure monad-par use, or being naughty, but the latter disables safe haskell.
Yet that shouldn't be necessary, {liftIO, runParIO}
is a valid, Safe-Haskell set of operations. Hence Adam's proposal for the trio of modules above and the newtype that would make sure that the programmer who uses liftIO
can't also use run
.
From a Safe Haskell perspective, the following combination is safe:
However, if this instance exists alongside
runPar :: Par a -> a
, safety is gone.A brute-force solution might involve a triangular module structure along with generous amounts of newtype deriving: