Incoherent instances can make ST monad unsound
In the recent message about regions I wrote:
Typeable constraint has reduced the problem of 'region nesting' to the regular problem of the 'linearity' of computations -- which is already solved in ST monad. We can add that pervasive 's' type parameter to our Q and IOM types. However, the simpler approach is just to use our 'mark' as that 's' parameter.
A small qualification should be added: although adding the 's' parameter in addition to the unexported Z parameter we had before is sound in all circumstances, the `simpler approach' may actually fail: it is possible to declare an instance "Typeable a" -- and given enough extensions, persuade GHC *and* Hugs to accept the code. The problem with IO regions would be the least of our worries however: in these circumstances, many things break, including the ST monad. One can indeed break the essential guarantee of the ST monad -- for example, create a top level STRef *and* fruitfully use in arbitrary ST computations. The enclosed code does exactly that. Thus, unsafePerformST becomes expressible in Haskell, given enough features. The presence of top-level mutable cells breaks the referential transparency. Hopefully the authors of Haskell' and Haskell2 would attach all-upper-case warnings to these extensions. The code below runs with GHC 6.4.1 (extensions are indicated inline). It also runs with Hugs, as hugs -98 +O /tmp/st.hs {-# OPTIONS -fglasgow-exts #-} {-# OPTIONS -fallow-undecidable-instances #-} {-# OPTIONS -fallow-overlapping-instances #-} {-# OPTIONS -fallow-incoherent-instances #-} module STTest where import Control.Monad.ST import Data.Typeable import Data.STRef import Data.Dynamic instance Typeable a where typeOf _ = mkTyConApp (mkTyCon "Anything goes") [] -- test1 = runST ( newSTRef 'a' ) leakedST = runST ( newSTRef 'a' >>= return . toDyn ) test3 :: Char test3 = runST (readSTRef $ ((fromDyn leakedST undefined)::STRef s Char)) {- result: *STTest> leakedST <<STRef Anything goes Char>> *STTest> test3 'a' -}
On Thu, 2006-01-26 at 20:29 -0800, oleg@pobox.com wrote:
circumstances, many things break, including the ST monad. One can indeed break the essential guarantee of the ST monad -- for example, create a top level STRef *and* fruitfully use in arbitrary ST computations. The enclosed code does exactly that. Thus, unsafePerformST becomes expressible in Haskell, given enough features. The presence of top-level mutable cells breaks the referential transparency. Hopefully the authors of Haskell' and Haskell2 would attach all-upper-case warnings to these extensions.
I don't think this is the extensions' fault; the problem is rather a bogus Typeable instance which basically gives you unsafeCoerce#. In fact, the code below compiles without any extensions and also breaks pretty much every static guarantee. import Data.Typeable import Data.Dynamic newtype Foo a = Foo { runFoo :: a ()} newtype Const a b = Const { runConst :: a } instance Typeable (Foo a) where typeOf _ = mkTyConApp (mkTyCon "Anything goes") [] coerce :: a -> b coerce = runConst . runFoo . flip fromDyn undefined . toDyn . Foo .Const
participants (2)
-
oleg@pobox.com -
Thomas Jäger