[Fwd: Re: [Haskell-cafe] Typeclasses and GADT [Was: Regular
Expressions without GADTs]]
kahl at cas.mcmaster.ca
kahl at cas.mcmaster.ca
Wed Oct 26 10:11:25 EDT 2005
Tomasz Zielonka <tomasz.zielonka at gmail.com> wrote
Wed, 26 Oct 2005 13:37:29 +0200:
>
> Speaking about casts, I was playing with using GADTs to create a
> non-extensible version of Data.Typeable and Data.Dynamic.
> I wonder if it's possible to write such a thing without GADTs (and
> unsafeCoerce, which is used in Data.Dynamic, IIRC).
It is possible to get even closer to Typeable by using
the same interface --- which is sufficient for some applications.
Otherwise, what I did recently is essentially equivalent
to the ``non-Dynamic part'' of Tomasz code
(and adding Dyn would of course work in the same way):
Thomasz' --> attached modules
-------------------------------------------------------
data Type --> data TI.TI*
class Typed --> class Typeable.Typeable*
newtype Wrapper* --> replaced by the ``*''s above ;-)
Wolfram
--[[application/octet-stream
Content-Disposition: attachment; filename="TI.lhs"][quoted-printable]]
%include lhs2TeX.fmt
%include WKlhs.sty
\section{Type Indices}
%{-# OPTIONS_GHC -fglasgow-exts #-} taken out for lhs2tex
\begin{code}
module TI where
import NatNum
\end{code}
For each ground type |a| constructed only from supported type constructors,
there is exactly one \emph{type index} in |TI a|.
In that respect,
an element of |TI a|
is similar to the satisfaction of the constraint |Typeable a|.
The important difference between |TI a| and |Typeable a|
is that structural induction over the structure of a type
documented by a |TI a| contains references to the constituent types
\emph{at the type level},
while structural induction over |TypeRep| representations of
type structure made accessible by |Typeable a|
has no connection to the type level at all.
\begin{code}
data TI :: * -> * where
Triv :: TI ()
Bool :: TI Bool
Char :: TI Char
Nat :: TI Nat
Int :: TI Int
Integer :: TI Integer
Float :: TI Float
Double :: TI Double
Apply_0 :: TI_0 t -> TI a -> TI (t a)
Apply_1 :: TI_1 t -> TI_0 a -> TI (t a)
\end{code}
Since one of the limitations of |Data.Typeable|
is its naming scheme that does not accommodate higher-kinded type constructors,
we use a naming scheme that is at least somewhat open in that direction,
and provide
alternative names following the scheme of |Data.Typeable| as type synonyms.
\begin{code}
type TI1 = TI_0
type TI2 = TI_0_0
type TI3 = TI_0_0_0
\end{code}
\begin{code}
data TI_0 :: (* -> *) -> * where
Maybe :: TI_0 Maybe
List :: TI_0 []
Apply_0_0 :: TI_0_0 t -> TI a -> TI_0 (t a)
\end{code}
\begin{code}
data TI_0_0 :: (* -> * -> *) -> * where
Either :: TI_0_0 Either
Pair :: TI_0_0 (,)
Fct :: TI_0_0 (->)
Apply_0_0_0 :: TI_0_0_0 t -> TI a -> TI_0_0 (t a)
\end{code}
\begin{code}
data TI_0_0_0 :: (* -> * -> * -> *) -> * where
Tup3 :: TI_0_0_0 (,,)
\end{code}
Just to demonstrate a higher-order kind in this context,
we define the datatype recursion type constructor.
\begin{code}
data TI_1 :: ((* -> *) -> *) -> * where
Fix :: TI_1 DFix
data DFix f = DFix (f (DFix f))
\end{code}
Defining type index equality without requiring identical types
makes the implementation of the cast functions much easier.
\begin{code}
eqTI :: TI a -> TI b -> Bool
eqTI Triv Triv = True
eqTI Bool Bool = True
eqTI Char Char = True
eqTI Nat Nat = True
eqTI Int Int = True
eqTI Integer Integer = True
eqTI Float Float = True
eqTI Double Double = True
eqTI (Apply_0 t a) (Apply_0 t' a') = eqTI_0 t t' && eqTI a a'
eqTI (Apply_1 t a) (Apply_1 t' a') = eqTI_1 t t' && eqTI_0 a a'
eqTI _ _ = False
\end{code}
\begin{code}
eqTI_0 :: TI_0 t -> TI_0 t' -> Bool
eqTI_0 Maybe Maybe = True
eqTI_0 List List = True
eqTI_0 (Apply_0_0 t a) (Apply_0_0 t' a') = eqTI_0_0 t t' && eqTI a a'
eqTI_0 _ _ = False
\end{code}
\begin{code}
eqTI_1 :: TI_1 t -> TI_1 t' -> Bool
eqTI_1 Fix Fix = True
eqTI_1 _ _ = False
\end{code}
\begin{code}
eqTI_0_0 :: TI_0_0 t -> TI_0_0 t' -> Bool
eqTI_0_0 Either Either = True
eqTI_0_0 Pair Pair = True
eqTI_0_0 Fct Fct = True
eqTI_0_0 _ _ = False
\end{code}
%{{{ EMACS lv
% Local Variables:
% folded-file: t
% fold-internal-margins: 0
% eval: (fold-set-marks "%{{{ " "%}}}")
% eval: (fold-whole-buffer)
% end:
%}}}
--[[application/octet-stream
Content-Disposition: attachment; filename="Typeable.lhs"][quoted-printable]]
%include lhs2TeX.fmt
%include WKlhs.sty
\section{Home-made |Typeable|}
%{-# OPTIONS_GHC -fglasgow-exts #-} taken out for lhs2tex
\begin{code}
module Typeable where
import NatNum
import TI
import GHC.Base ( unsafeCoerce# )
\end{code}
This module is currently happily restricting itself to Glasgow Haskell.
Many things that are done in a more portable way in |Data.Typeable|
are done in a more concise, but GHC-specific way here.
The following function is GHC-specific:
\begin{code}
unsafeCoerce :: a -> b
unsafeCoerce = unsafeCoerce#
\end{code}
For the tyme being, we only go to |Typeable3|,
while |Data.Typeable| goes all the way to |Typeable7|.
\begin{code}
class Typeable a where typeIndex :: a -> TI a
class Typeable1 t where typeIndex1 :: t a -> TI1 t
class Typeable2 t where typeIndex2 :: t a b -> TI2 t
class Typeable3 t where typeIndex3 :: t a b c -> TI3 t
\end{code}
We shamelessly use scoped type variables
to abbreviate the |cast| and |gcast| definitions,
which otherwise correspond directly to those from |Data.Typeable|.
\begin{code}
cast :: (Typeable a, Typeable b) => a -> Maybe b
cast x :: Maybe b = if typeIndex x `eqTI` typeIndex (undefined :: b)
then Just $ unsafeCoerce x
else Nothing
\end{code}
\begin{code}
gcast :: (Typeable a, Typeable b) => c a -> Maybe (c b)
gcast (x :: c a) :: Maybe (c' b) =
if typeIndex (undefined :: a) `eqTI` typeIndex (undefined :: b)
then Just $ unsafeCoerce x
else Nothing
\end{code}
\begin{code}
gcast1 :: (Typeable1 t, Typeable1 t') => c (t a) -> Maybe (c (t' a))
gcast1 (x :: c (t a)) :: Maybe (c' (t' a')) =
if typeIndex1 (undefined :: t a) `eqTI_0` typeIndex1 (undefined :: t' a)
then Just $ unsafeCoerce x
else Nothing
\end{code}
\begin{code}
gcast2 :: (Typeable2 t, Typeable2 t') => c (t a b) -> Maybe (c (t' a b))
gcast2 (x :: c (t a b)) :: Maybe (c' (t' a' b')) =
if typeIndex2 (undefined :: t a b) `eqTI_0_0` typeIndex2 (undefined :: t' a b)
then Just $ unsafeCoerce x
else Nothing
\end{code}
%{{{ automatic instances
For the automatic instances, the machinery from |Data.Typeable|
can easily be adapted.
\begin{code}
instance (Typeable1 s, Typeable a) => Typeable (s a) where typeIndex = typeIndexDefault
instance (Typeable2 s, Typeable a) => Typeable1 (s a) where typeIndex1 = typeIndex1Default
instance (Typeable3 s, Typeable a) => Typeable2 (s a) where typeIndex2 = typeIndex2Default
\end{code}
\begin{code}
-- | For defining a 'Typeable' instance from any 'Typeable1' instance.
typeIndexDefault :: (Typeable1 t, Typeable a) => t a -> TI (t a)
typeIndexDefault (x :: t a) = typeIndex1 x `Apply_0` typeIndex (undefined :: a)
-- | For defining a 'Typeable1' instance from any 'Typeable2' instance.
typeIndex1Default :: (Typeable2 t, Typeable a) => t a b -> TI_0 (t a)
typeIndex1Default (x :: t a b) = typeIndex2 x `Apply_0_0` typeIndex (undefined :: a)
-- | For defining a 'Typeable2' instance from any 'Typeable3' instance.
typeIndex2Default :: (Typeable3 t, Typeable a) => t a b c -> (TI_0_0 (t a))
typeIndex2Default (x :: t a b c) = typeIndex3 x `Apply_0_0_0` typeIndex (undefined :: a)
\end{code}
%}}}
%{{{ standard library instances
With our standard library instances
we are obviously restricted to the types covered by |TI|.
\begin{code}
instance Typeable () where typeIndex _ = Triv
instance Typeable Bool where typeIndex _ = Bool
instance Typeable Char where typeIndex _ = Char
instance Typeable Nat where typeIndex _ = Nat
instance Typeable Int where typeIndex _ = Int
instance Typeable Integer where typeIndex _ = Integer
instance Typeable Float where typeIndex _ = Float
instance Typeable Double where typeIndex _ = Double
instance Typeable1 Maybe where typeIndex1 _ = Maybe
instance Typeable1 [] where typeIndex1 _ = List
instance Typeable2 Either where typeIndex2 _ = Either
instance Typeable2 (,) where typeIndex2 _ = Pair
instance Typeable2 (->) where typeIndex2 _ = Fct
\end{code}
%}}}
%{{{ EMACS lv
% Local Variables:
% folded-file: t
% fold-internal-margins: 0
% eval: (fold-set-marks "%{{{ " "%}}}")
% eval: (fold-whole-buffer)
% end:
%}}}
--[[application/octet-stream
Content-Disposition: attachment; filename="GCast.lhs"][quoted-printable]]
%include lhs2TeX.fmt
%include WKlhs.sty
\section{Minimal Interface Providing Generic Cast}
This module provides a minimal interface to |gcast|
since this is all that is really required by many applications
of |Typeable| and friends.
Isolating this interface
makes it easier to switch between different implementations.
%{-# OPTIONS_GHC -fglasgow-exts #-} taken out for lhs2tex
\begin{code}
module GCast
( Typeable()
, Typeable1()
, Typeable2()
, Typeable3()
, cast
, gcast
, gcast1
, gcast2
) where
-- import Data.Typeable
import Typeable
\end{code}
%{{{ EMACS lv
% Local Variables:
% folded-file: t
% fold-internal-margins: 0
% eval: (fold-set-marks "%{{{ " "%}}}")
% eval: (fold-whole-buffer)
% end:
%}}}
--[[application/octet-stream
Content-Disposition: attachment; filename="NatNum.lhs"][quoted-printable]]
\section{Inductive Definition of Natural Numbers}
\begin{code}
module NatNum where
import Data.Typeable
data Nat = Zero | Succ
deriving Typeable
\end{code}
More information about the Haskell-Cafe
mailing list