monad-par icon indicating copy to clipboard operation
monad-par copied to clipboard

Investigate Safe Haskell issues wrt MonadIO instances and run interfaces

Open acfoltzer opened this issue 13 years ago • 5 comments
trafficstars

From a Safe Haskell perspective, the following combination is safe:

instance MonadIO Par
runParIO :: Par a -> IO a

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:

module SMP.Internal (Par, runPar, runParIO) where
newtype Par a = ...
  deriving (MonadIO ...)
runPar = ...
runParIO = ...
{-# LANGUAGE Safe #-}
module SMP.IO (Par, runParIO) where
-- use Par and runParIO from SMP.Internal
{-# LANGUAGE Safe #-}
module SMP (Par, runPar) where
newtype Par a = Par (SMP.Internal.Par a)
  deriving ({- not MonadIO -})
runPar = SMP.Internal.runPar . unPar

acfoltzer avatar Apr 16 '12 16:04 acfoltzer

I don't presently see a better solution than this, though certainly we'd like to scrap that boilerplate if we could.

rrnewton avatar Apr 16 '12 17:04 rrnewton

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 :)

simonmar avatar Apr 17 '12 07:04 simonmar

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.

acfoltzer avatar Apr 17 '12 17:04 acfoltzer

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!).

simonmar avatar Apr 18 '12 08:04 simonmar

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.

rrnewton avatar Apr 18 '12 14:04 rrnewton