Type safe readonly and writeonly attributes with phantom types
Nick Name <[email protected]>
| Newsgroups | gmane.comp.lang.haskell.gui |
|---|---|
| Message-ID | <[email protected]> |
Yes, I ask dumb questions on haskell-cafe for a reason ;)
As the subject already states, I think that it's worth to be considered
to use phantom types and type classes to encode the access mode of an
attribute.
A first approach is the following:
---- Begin 1 ----
class ReadMode mode
class WriteMode mode
class Mode a
data Readable = Readable
data Writeable = Writeable
data ReadWrite = ReadWrite
instance ReadMode Readable
instance WriteMode Writeable
instance ReadMode ReadWrite
instance WriteMode ReadWrite
instance ReadMode a => ReadMode (a,b)
instance WriteMode b => WriteMode (a,b)
data Attr m w a = Attr (w -> IO a) (w -> a -> IO ())
newAttr :: (w -> IO a) -> (w -> a -> IO ()) -> Attr ReadWrite w a
readAttr :: (w -> IO a) -> Attr Readable w a
writeAttr :: (w -> a -> IO ()) -> Attr Writeable w a
get :: ReadMode m => w -> Attr m w a -> IO a
---- End 1 ----
However this has a problem: the combinatorial explosion of data
declarations to represent modes, if one has more than two
"switches" (as I will have in the next days ;-)
The second one, wich I particularly like, is:
---- Begin 2 ----
class ReadMode mode
class WriteMode mode
class Mode a
data Readable = Readable
data Writeable = Writeable
instance Mode Readable
instance Mode Writeable
instance ReadMode Readable
instance WriteMode Writeable
instance (Mode a,Mode b,ReadMode a) => ReadMode (a,b)
instance (Mode a,Mode b,WriteMode b) => WriteMode (a,b)
data Attr m w a = Attr (w -> IO a) (w -> a -> IO ())
newAttr :: (w -> IO a) -> (w -> a -> IO ()) ->
Attr (Readable,Writeable) w a
readAttr :: (w -> IO a) -> Attr Readable w a
writeAttr :: (w -> a -> IO ()) -> Attr Writeable w a
data Prop ...
(=:) :: WriteMode m => Attr m w a -> a -> Prop w
get :: ReadMode m => w -> Attr m w a -> IO a
set :: w -> [Prop w] -> IO ()
---- End 2 ----
Note that this is everything haskell98, and can be used (yes, I still
haven't tried to recompile htoolkit, but going to try now) like Attr.hs
is used right now.
This also can solve the already mentioned problem wich is:
"I want to breakdown my types in a class hierarchy, so I want to define
thing that, say, have a position. So I want to put the type "Menu" in
that class, and also the type "Window". But a window can be
repositioned, while a menu can't. So I have to make two classes, if I
have separated "ReadOnlyAttr" and "ReadWriteAttr" at the type level."
To solve this problem, unfortunately, I need multi-parameter type
classes, wich could be considered almost legal, but also functional
dependencies if I want to avoid type declarations everywhere (and even
in places where they aren't haskell98 anyway):
---- Begin 3 ----
class HasMyAttr w m | w -> m where
myAttr :: Attr m w ()
data TestRW = TestRW
data TestR = TestR
instance HasMyAttr TestRW (Readable,Writeable) where
myAttr = newAttr undefined undefined
instance HasMyAttr TestR Readable where
myAttr = readAttr undefined
t1 = set1 TestRW (myAttr =: ())
t2 = get TestRW myAttr
t3 = set1 TestR (myAttr =: ()) -- this row can't be compiled
t4 = get TestR myAttr
---- End 3 ----
But note how self documenting this code is: the two instance
declarations say exactly that "myAttr" is readable and writeable in
type TestRW, and read-only in testR. Besides, note that the mode system
is extensible: no one constrains an attribute to have a mode wich is
one of the types here mentioned, an user has just to conscientiously
put its own modes into the right typeclasses. However, if we wish not
to allow extention, we still can in some way.
Besides, I think that this mode system could be used even if one does
not want h98 extentions: it does not limit in any way (AFAIK) the
standard (type-correct) usage wrt the present implementation and adds
stronger type-checking.
I attach the complete modified Attr.hs for reference and wait hopefully
for some comment.
Vincenzo
_______________________________________________
GUI mailing list
[email protected]
http://www.haskell.org/mailman/listinfo/gui
Attr.hs
(text/x-c++src, 2.9 KB)
module Attr where
class ReadMode mode
class WriteMode mode
class Mode a
data Readable = Readable
data Writeable = Writeable
instance Mode Readable
instance Mode Writeable
instance ReadMode Readable
instance WriteMode Writeable
instance (Mode a,Mode b,ReadMode a) => ReadMode (a,b)
instance (Mode a,Mode b,WriteMode b) => WriteMode (a,b)
{--------------------------------------------------------------------
Attributes
--------------------------------------------------------------------}
-- | Widgets @w@ can have attributes of type @a@ with mode @m@
data Attr m w a = Attr (w -> IO a) (w -> a -> IO ())
-- | Create a new attribute with a specified getter and setter function.
newAttr :: (w -> IO a) -> (w -> a -> IO ()) -> Attr (Readable,Writeable) w a
newAttr getter setter
= Attr getter setter
-- | Define a read-only attribute. Takes the name of the attribute as its first argument.
readAttr :: (w -> IO a) -> Attr Readable w a
readAttr getter
= Attr getter (\ _ _ -> return ())
-- | Define a write-only attribute. Takes the name of the attribute as its first argument.
writeAttr :: (w -> a -> IO ()) -> Attr Writeable w a
writeAttr setter
= Attr (\ _ -> undefined) setter
-- | Properties
data Prop w = Prop{ propSet :: (w -> IO ())
, propRestore :: (w -> IO (IO ())) -- a function that returns a restoration function :-)
}
newProp :: (w -> IO ()) -> (w -> IO a) -> (w -> a -> IO ()) -> Prop w
newProp action getter setter
= Prop action (\w -> do oldx <- getter w; return (setter w oldx))
-- | Associate an attribute with a new value into a property.
(=:) :: WriteMode m => Attr m w a -> a -> Prop w
(=:) (Attr getter setter) x = newProp (\w -> setter w x) getter setter
-- | Get an attribute
get :: ReadMode m => w -> Attr m w a -> IO a
get w (Attr getter setter)
= getter w
-- | Set properties
set :: w -> [Prop w] -> IO ()
set w props
= mapM_ (set1 w) props
set1 :: w -> Prop w -> IO ()
set1 w (Prop setter mkrestore)
= setter w
ro = readAttr undefined
wo = writeAttr undefined
--f = get () wo
--g = set () [ro =: ()]
-- | (@mapAttr get set attr@) maps an attribute of @Attr w a@ to
-- @Attr w b@ where (@get :: a -> b@) is used when the attribute is
-- requested and (@set :: a -> b -> a@) is applied to current
-- value when the attribute is set.
mapAttr :: (ReadMode m,WriteMode m) => (a -> b) -> (a -> b -> a) -> Attr m w a -> Attr m w b
mapAttr get set (Attr getter setter)
= Attr (\w -> do a <- getter w; return (get a))
(\w b -> do a <- getter w; setter w (set a b))
class HasMyAttr w m | w -> m where
myAttr :: Attr m w ()
data TestRW = TestRW
data TestR = TestR
instance HasMyAttr TestRW (Readable,Writeable) where
myAttr = newAttr undefined undefined
instance HasMyAttr TestR Readable where
myAttr = readAttr undefined
t1 = set1 TestRW (myAttr =: ())
t2 = get TestRW myAttr
--t3 = set1 TestR (myAttr =: ()) -- uncomment this to enjoy your precise errors!!
t4 = get TestR myAttr