A monad for type casting
Koen Claessen
koen@cs.chalmers.se
Wed, 19 Sep 2001 09:50:38 +0200 (MET DST)
Hi all,
Here is a little experiment I did a while ago. I was trying
to isolate the capability of the ST monad to deal with
different types at the same time. (Following up on a
discussion at the Haskell Workshop in 2000, where we
discussed that the ST monad is really a combination of
different features that should be separated.)
The idea is that one has a monad, "O s", in which one can
create objects with identity, of type "Object s a":
type O s a -- abstract, Monad
type Object s a -- abstract
newObject :: O s (Object s a)
The idea is now that, we want to be able to compare these
objects for equality. But not only that! Given two objects
of possibly different types, say "Object s a" and "Object s
b", if they are equal, then so must their types be!
So, instead of making "Object s a" an instance of "Eq", we
provide the following function:
compareObject :: Object s a -> Object s b -> Maybe (a -> b)
If the objects are not equal, we get "Nothing". But, if the
objects are equal, we also get a "proof" of that you can
cast between their types.
This monad is safe, and can be used as a basis for monads in
which one wants to implement something like "STRef"s. I
provide an implementation of an (inefficient, but complete)
ST monad, only using the O monad.
I conjecture this functionality cannot be implemented in
Haskell 98, nor in any of the known safe extensions of
Haskell. Here, I provide an implementation that works with
Hugs.
Any comments?
One thing I would like to propose for GHC and Hugs is to
provide a function like "compareObject" for "STRef"s and
"IORef"s.
Regards,
/Koen.
=====
module Object
( O -- :: * -> * -> *; Monad
, Object -- :: * -> * -> *
, newObject -- :: O s (Object s a)
, compareObject -- :: Object s a -> Object s b -> Maybe (a -> b)
, runO -- :: (forall s . O s a) -> a
)
where
-------------------------------------------------------------------
-- This module is an attempt to isolate the capability of the ST
-- monad of dealing with several types at the same time.
--
-- The idea is that objects of type "Object s a" represent `objects
-- with identity', that are associated with a type a. There exists
-- a comparison function that checks if two objects have the same
-- identity. If this is the case, the associated types should be
-- the same too, and a witness function is provided.
--
-- To ensure that objects with the same identity really are the
-- same object, and also to avoid polymorphically typed objects,
-- we have to create the objects in a monad. We also have to use the
-- "trick with the s", also used in the realization of the ST
-- monad in Haskell. This trick also ensures we can never compare
-- two "Object"s from two different "O"-threads.
--
-- I conjecture that it is impossible to implement this module in
-- pure Haskell with any of the known safe extensions. (So it is
-- even impossible if you are allowed to use the ST monad.)
--
-- Koen Claessen, 19 November 2000
-------------------------------------------------------------------
-------------------------------------------------------------------
-- type O, type Object:
-- "O" is a state monad keeping track of unique tags. "Object"
-- only contains a tag for checking equality.
newtype O s a = MkO (Int -> (a, Int))
newtype Object s a = MkObject Int
instance Monad (O s) where
return x = MkO (\n -> (x, n))
MkO m >>= f = MkO (\n -> let (a, n') = m n ; MkO m' = f a in m' n')
-------------------------------------------------------------------
-- newObject:
-- creating a new object is creating a new tag.
newObject :: O s (Object s a)
newObject = MkO (\n -> (MkObject n, n+1))
-------------------------------------------------------------------
-- compareObject:
-- We check if the objects are equal. If so, we know we can safely
-- cast.
compareObject :: Object s a -> Object s b -> Maybe (a -> b)
compareObject (MkObject tag1) (MkObject tag2)
| tag1 == tag2 = Just unsafeCoerce
| otherwise = Nothing
-- boo!
primitive unsafeCoerce "primUnsafeCoerce" :: a -> b
-------------------------------------------------------------------
-- runO:
-- Executing the state monad.
runO :: (forall s . O s a) -> a
runO m = let MkO f = m ; (a, _) = f 0 in a
-------------------------------------------------------------------
-- the end.
=====
module MyST
( ST -- :: * -> * -> *; Monad
, STRef -- :: * -> * -> *; Eq
, newSTRef -- :: a -> ST s (STRef s a)
, writeSTRef -- :: STRef s a -> a -> ST s ()
, readSTRef -- :: STRef s a -> ST s a
, runST -- :: (forall s . ST s a) -> a
)
where
-------------------------------------------------------------------
-- This module implements an inefficient version of the ST
-- monad in "normal" Haskell; the only extensions we use are
-- existential and universal quantification, and the "Object" module.
--
-- The implementation is inefficient because it uses lists as
-- lookup tables.
--
-- Koen Claessen, 19 November 2000
-------------------------------------------------------------------
import Object
( O
, Object
, newObject
, compareObject
, runO
)
-------------------------------------------------------------------
-- type ST, type State, type Entry, type STRef:
-- the implementation is straightforward: "ST" is a state monad
-- in a type "State", which is a lookup table of existentially
-- quantified "Entry"s. We also implement a weaker form of
-- equality on "STRef"s.
newtype ST s a = MkST (State s -> O s (a, State s))
type State s = [Entry s]
data Entry s = forall a . Object s a := a
newtype STRef s a = MkSTRef (Object s a)
instance Monad (ST s) where
return x = MkST (\n -> do return (x, n))
MkST m >>= f = MkST (\n -> do (a, n') <- m n ; let MkST m' = f a in m' n')
instance Eq (STRef s a) where
MkSTRef tp1 == MkSTRef tp2 =
case tp1 `compareObject` tp2 of
Just _ -> True
Nothing -> False
-------------------------------------------------------------------
-- newSTRef, readSTRef, writeSTRef, runST:
-- the only interesting things happening here are the safe casts
-- by "readSTRef" and "writeSTRef" (note that they are done in
-- different directions).
newSTRef :: a -> ST s (STRef s a)
newSTRef a = MkST $ \st ->
do tp <- newObject
return (MkSTRef tp, (tp := a) : st)
readSTRef :: STRef s a -> ST s a
readSTRef (MkSTRef tp) = MkST $ \st ->
do return (find st tp, st)
where
find ((tp1 := a) : st) tp2 =
case tp1 `compareObject` tp2 of
Just cast -> cast a
Nothing -> find st tp2
writeSTRef :: STRef s a -> a -> ST s ()
writeSTRef (MkSTRef tp) a = MkST $ \st ->
do return ((), update st tp a)
where
update (ent@(tp1 := a) : st) tp2 a' =
case tp2 `compareObject` tp1 of
Just cast -> (tp1 := cast a') : st
Nothing -> ent : update st tp2 a'
runST :: (forall s . ST s a) -> a
runST m = let MkST f = m in runO (do (a,_) <- f []; return a)
-------------------------------------------------------------------
-- the end.