Dynamic types: GHCI works, GHC doesn't?
Hi all,
I'm trying to get a grip on the Dynamic types stuff supplied with
GHC, and I'm not sure if I'm doing something wrong, or whether
I've found a bug.
It seems that the fromDynamic and fromDyn functions seem to work
if you load your module into GHCI, but they don't work when you
compile it into a stand-alone executable with GHC. Here's an
example of what the output is meant to be like:
22:51(0) .../project/dynamic_types-bug% ghci -package lang DynamicTypesBug.hs
[ GHCI header deleted ]
Loading package std ... linking ... done.
Loading package lang ... linking ... done.
Compiling Main ( DynamicTypesBug.hs, interpreted )
Ok, modules loaded: Main.
Main> main
Static type: FootnoteData [Char] Int
Coerced (dynamic) type: FootnoteData [Char] Int
Does dynamic type == static type? True
Attempting to run fromDynamic on the dynamic type ...
5. This be the footnote
Main>
So, that all works fine. Compile it through GHC, however, and
this happens:
22:57(0) .../project/dynamic_types-bug% ghc --make -package lang DynamicTypesBug.hs
ghc-5.02.2: chasing modules from: DynamicTypesBug.hs
Compiling Main ( DynamicTypesBug.hs, ./DynamicTypesBug.o )
ghc: linking ...
22:57(0) .../project/dynamic_types-bug% ./a.out
Static type: FootnoteData [Char] Int
Coerced (dynamic) type: FootnoteData [Char] Int
Does dynamic type == static type? False
Attempting to run fromDynamic on the dynamic type ...
Fail: Maybe.fromJust: Nothing
Furthermore, if you compile it with GHC, then run GHCI afterward,
GHCI will use the Main.o object file rather than try to compile
it again, and will fail:
22:58(1) .../project/dynamic_types-bug% ghci -package lang DynamicTypesBug.hs
[ GHCI header deleted ]
Loading package std ... linking ... done.
Loading package lang ... linking ... done.
Skipping Main ( DynamicTypesBug.hs, ./DynamicTypesBug.o )
Ok, modules loaded: Main.
Main> main
Static type: FootnoteData [Char] Int
Coerced (dynamic) type: FootnoteData [Char] Int
Does dynamic type == static type? False
Attempting to run fromDynamic on the dynamic type ...
*** Exception: Maybe.fromJust: Nothing
Main>
I've seen this behaviour with:
* ghc-5.02.2 from the Debian ghc5 package
* ghc-5.02.1 on another Linux machine (compiled from source)
* ghc-5.03.20020204 snapshot on Windows (Sigbjorne's MSI package)
It's possible that I'm not using the Dynamic module functions
properly, since I don't think I understand them that well. In
particular, I'm a bit confused as to how to construct a TypeRep
with the mkAppTy function. See my code to see how I'm (mis)using
it :).
The code which exhibits this behaviour is attached to this
message (DynamicTypesBug.hs). Hopefully I'm just doing something
silly ...
--
#ozone/algorithm
Andre Pang
Hi all, I'm trying to get a grip on the Dynamic types stuff supplied with GHC, and I'm not sure if I'm doing something wrong, or whether I've found a bug.
Your Typeable instance looks like this:
instance Typeable FootnoteData where typeOf _ = mkAppTy (mkTyCon "FootnoteData") [typeOf ("Foo" :: String), typeOf (7 :: Int)]
This should be written:
instance Typeable FootnoteData where typeOf _ = mkAppTy fdtc [typeOf ("Foo" :: String), typeOf (7 :: Int)]
fdtc = mkTyCon "FootnoteData"
That is, the TyCon definition has to be a CAF (i.e., a top level definition with no arguments). The reason is somewhat grubby but comes down to 'things go much faster if you do this'). Here's the relevant comment from the code: -- If we enforce the restriction that there is only one -- @TyCon@ for a type & it is shared among all its uses, -- we can map them onto Ints very simply. The benefit is, -- of course, that @TyCon@s can then be compared efficiently. -- Provided the implementor of other @Typeable@ instances -- takes care of making all the @TyCon@s CAFs (toplevel constants), -- this will work. -- If this constraint does turn out to be a sore thumb, changing -- the Eq instance for TyCons is trivial. We've seen several bug reports of this form lately - can someone change the representation/ implementation to something a little less fragile? Three possibilities spring to mind: 1) The original slow implementation which uses string comparisions. This could be speeded up using unsafePtrEquality in the normal (Lisplike) way. 2) Maintain a table of all TyCons (or the stablename table??) When a TyCon thunk is evaluated, it looks itself up in the TyCon table inserting itself if necessary and updates itself with an index into the table. 3) Same as (2) except that if the lookup succeeds it knows there are two independent TyCons for the same string. One of two things must have happened: 1) Two independent types (perhaps in different modules) used the same string as their TyCon identifier. Yoiks! Report it as an error. 2) Someone forgot to make their TyCon a CAF. This could be made to work (using implementation strategy 2) but it will be slow. Best thing to do is report the problem and have the programmer fix it. I favour 1 or 3. While we're at it, I'd like to repeat my request that the word 'unsafe' be made part of the classname Typeable or the method name typeOf. The reason is that a badly written Typeable definition can break typesafety. Here's a superficially plausible but broken instance for IO which demonstrates it
instance Typeable (IO a) where typeOf _ = mkAppTy iotc []
iotc = mkTyCon "IO"
With this definition typeOf (return 1 :: IO Int) == typeOf (return () :: IO ()) and so you can coerce back and forth between these types using from/toDynamic. [A better fix would be for compilers to generate the (somewhat tricky) Typeable instances themselves.] -- Alastair Reid reid@cs.utah.edu http://www.cs.utah.edu/~reid/
On Sat, Jun 01, 2002 at 07:56:39PM +0100, Alastair Reid wrote:
Your Typeable instance looks like this:
instance Typeable FootnoteData where typeOf _ = mkAppTy (mkTyCon "FootnoteData") [typeOf ("Foo" :: String), typeOf (7 :: Int)]
This should be written:
instance Typeable FootnoteData where typeOf _ = mkAppTy fdtc [typeOf ("Foo" :: String), typeOf (7 :: Int)]
fdtc = mkTyCon "FootnoteData"
That is, the TyCon definition has to be a CAF (i.e., a top level definition with no arguments). The reason is somewhat grubby but comes down to 'things go much faster if you do this').
I see, thanks. I'll try to update the documentation with this note.
While we're at it, I'd like to repeat my request that the word 'unsafe' be made part of the classname Typeable or the method name typeOf. The reason is that a badly written Typeable definition can break typesafety. Here's a superficially plausible but broken instance for IO which demonstrates it
instance Typeable (IO a) where typeOf _ = mkAppTy iotc []
iotc = mkTyCon "IO"
With this definition
typeOf (return 1 :: IO Int) == typeOf (return () :: IO ())
and so you can coerce back and forth between these types using from/toDynamic.
Ahem, this is what I actually did to get my Dynamic types working
in the first place (oops) :). mkAppTy was being given an empty
list of TypeReps, since I was confused about how to generate
those TypeReps.
It would be much neater if you could do something like
instance Typeable Foo where
typeOf _ = mkAppTy constructor [Char, Char, Int]
This is what I expected the Dynamic module interface would look
like, since when do you "typeOf 'a'" in GHCI, it does reply with
"Char".
I guess a possible workaround would be something like
charT = typeOf ('a' :: Char)
intT = typeOf (69 :: Int)
instance Typeable Foo where
typeOf _ = mkAppTy constructor [charT, charT, intT]
which is what I'm doing right now. Maybe these type definitions
can go into the Dynamic module itself?
--
#ozone/algorithm
just as a note, the new version of DrIFT (which i now maintain) has the ability to derive 'Typeable', which is used for the Strafunski generic programming representation (which it also can derive..) the homepage is now at 'http://repetae.net/john/computer/haskell/DrIFT' I sort of fudged the Typeable derivation rule, if someone wanted to robustify it, i would gladly accept patches. I dont think that 'unsafe' need be added to Typeable since hopefully people will never be creating instances themselves, with the instances hidden behind DrIFT and/or 'deriving' clauses then they can be assured of being correct (well... as correct as anything is :)) . Typeable itself is not inherently unsafe, it is broken implementations of it which are. John On Sun, Jun 02, 2002 at 12:11:49PM +1000, Andre Pang wrote:
On Sat, Jun 01, 2002 at 07:56:39PM +0100, Alastair Reid wrote:
Your Typeable instance looks like this:
instance Typeable FootnoteData where typeOf _ = mkAppTy (mkTyCon "FootnoteData") [typeOf ("Foo" :: String), typeOf (7 :: Int)]
This should be written:
instance Typeable FootnoteData where typeOf _ = mkAppTy fdtc [typeOf ("Foo" :: String), typeOf (7 :: Int)]
fdtc = mkTyCon "FootnoteData"
That is, the TyCon definition has to be a CAF (i.e., a top level definition with no arguments). The reason is somewhat grubby but comes down to 'things go much faster if you do this').
I see, thanks. I'll try to update the documentation with this note.
While we're at it, I'd like to repeat my request that the word 'unsafe' be made part of the classname Typeable or the method name typeOf. The reason is that a badly written Typeable definition can break typesafety. Here's a superficially plausible but broken instance for IO which demonstrates it
instance Typeable (IO a) where typeOf _ = mkAppTy iotc []
iotc = mkTyCon "IO"
With this definition
typeOf (return 1 :: IO Int) == typeOf (return () :: IO ())
and so you can coerce back and forth between these types using from/toDynamic.
Ahem, this is what I actually did to get my Dynamic types working in the first place (oops) :). mkAppTy was being given an empty list of TypeReps, since I was confused about how to generate those TypeReps.
It would be much neater if you could do something like
instance Typeable Foo where typeOf _ = mkAppTy constructor [Char, Char, Int]
This is what I expected the Dynamic module interface would look like, since when do you "typeOf 'a'" in GHCI, it does reply with "Char".
I guess a possible workaround would be something like
charT = typeOf ('a' :: Char)
intT = typeOf (69 :: Int)
instance Typeable Foo where typeOf _ = mkAppTy constructor [charT, charT, intT]
which is what I'm doing right now. Maybe these type definitions can go into the Dynamic module itself?
-- #ozone/algorithm
- b.sc (comp. sci+psych) _______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users
-- --------------------------------------------------------------------------- John Meacham - California Institute of Technology, Alum. - john@foo.net ---------------------------------------------------------------------------
just as a note, the new version of DrIFT (which i now maintain) has the ability to derive 'Typeable',
Very cool. (Though I'd still like to see Typeable being moved into compilers.)
I sort of fudged the Typeable derivation rule, if someone wanted to robustify it, i would gladly accept patches.
You should add a few test cases involving functions to your instance - dashed tricky to get those ones working. As a challenge, here's a typical state-passing monad datatype: newtype M s a = M (s -> (s,a))
I dont think that 'unsafe' need be added to Typeable since hopefully people will never be creating instances themselves, with the instances hidden behind DrIFT and/or 'deriving' clauses then they can be assured of being correct (well... as correct as anything is :)) .
Sadly, most people aren't using DriFT for that and it seems likely that people will continue writing them by hand and will continue to get them wrong.
Typeable itself is not inherently unsafe, it is broken implementations of it which are.
The same situation applies with the use of unsafePerformIO. There's nothing wrong with using unsafePerformIO (well, it is semantically a little ugly but apart from that...) - the problem is people misusing it. The idea of putting the word 'unsafe' in the identifiers is: 1) To try to get people to wake up and start thinking hard about what they're doing when they write these instances (or, if they used DriFT to generate them, to make them wake up if they start modifying them by hand). 2) To make it easy to home in on problematic pieces of the system when it starts disobeying Haskell rules. Typesafety broken? grep for 'unsafe'. Referential transparency broken? grep for 'unsafe'. Received puzzling bug report? grep for 'unsafe'. -- Alastair
participants (4)
-
Alastair Reid -
Andre Pang -
Andre Pang -
John Meacham