Newtype unwrapping in the FFI
[This email concerns an infelicity in the FFI spec. I'm directing it primarily to the Haskell Prime mailing list, but ccing the libraries list so that people there know about the thread. I suggest that replies go to Haskell Prime only.] Consider this program (see http://hackage.haskell.org/trac/ghc/ticket/3008) module NT( N ) where newtype N = MkN Int module FFI where foreign import f :: N - IO () Is module FFI OK? It would definitely be OK if N was defined in the module FFI: the FFI spec says that the compiler must automatically unwrap any newtype arguments. http://www.cse.unsw.edu.au/~chak/haskell/ffi/ffi/ffise3.html#x6-120003.2 But it's less clear when N is defined in another module *and* its representation isn't exported. The author of NT might believe that because N's representation is hidden, she can change it to, say data N = MkN Int without affecting the rest of the world. But she can't. This is a nasty failure of abstraction. It is, I believe the only way in which a client of a NT could be affected by N's representation, even though that representation is allegedly hidden. (ToDo: check on generalised newtype deriving.) This seems Bad to me. Indeed, the cause of the above bug report is that GHC's implementation assumes that the representation is fully hidden if the constructor is not exported, and does not expose the representation of N even to separately-compiled modules (at least when you are not using -O). But the point here is not what GHC stumbles on but what is supposed to happen. Maybe we should fix it. Proposal: * Clarify the spec to say that a newtype can only be automatically unwrapped if the newtype constructor (MkN in this case) is in scope It happens that a large set of types from Foreign.C.Types, such as CInt and friends, are exported without their constructors, so adopting this new rule would require us to change Foreign.C.Types to expose the representation of CInt and friends. (As it happens, nhc requires this already, so there's some #ifdeffery there already.) Simon ___ Haskell-prime mailing list Haskell-prime@haskell.org http://www.haskell.org/mailman/listinfo/haskell-prime
RE: Newtype unwrapping in the FFI
|* Clarify the spec to say that a newtype can only be automatically | unwrapped if the newtype constructor (MkN in this case) is in | scope | | I agree up to here. For user-defined types, not exporting the | constructor should be a guarantee of abstraction. | | It happens that a large set of types from Foreign.C.Types, such as | CInt and friends, are exported without their constructors, so adopting | this new rule would require us to change Foreign.C.Types to expose the | representation of CInt and friends. (As it happens, nhc requires this | already, so there's some #ifdeffery there already.) | | The thing about CInt though is that it is supposed to be abstract *and* | an FFI type. I want to think of it as a primitive FFI type (though it is | not a basic type as defined by the FFI). We don't want to know that on | some system it is Int32 and on others it is Int64. We do not want access | to the constructor here. Trouble is, there are a zillion types in Foreign.C.Types, and another zillion in Foreign.Posix.Types. Do you want to list them all as blessed in the FFI addendum? S ___ Haskell-prime mailing list Haskell-prime@haskell.org http://www.haskell.org/mailman/listinfo/haskell-prime
Re: Newtype unwrapping in the FFI
Simon Peyton-Jones: |* Clarify the spec to say that a newtype can only be automatically | unwrapped if the newtype constructor (MkN in this case) is in | scope | | I agree up to here. For user-defined types, not exporting the | constructor should be a guarantee of abstraction. I also don't really see an alternative. The ability to pass a type to a foreign function tells us something about the type; there is not much we can do about that. | It happens that a large set of types from Foreign.C.Types, such as | CInt and friends, are exported without their constructors, so adopting | this new rule would require us to change Foreign.C.Types to expose the | representation of CInt and friends. (As it happens, nhc requires this | already, so there's some #ifdeffery there already.) | | The thing about CInt though is that it is supposed to be abstract *and* | an FFI type. I want to think of it as a primitive FFI type (though it is | not a basic type as defined by the FFI). We don't want to know that on | some system it is Int32 and on others it is Int64. We do not want access | to the constructor here. Trouble is, there are a zillion types in Foreign.C.Types, and another zillion in Foreign.Posix.Types. Do you want to list them all as blessed in the FFI addendum? The types in Foreign.C.Types are already listed in the FFI addendum (it actually specifies the whole module, just like the report specifies what is in the Prelude). However, I think an argument can be made that hiding the implementation of the types in C.Foreign.Types is a Bad Thing. FFI code is inherently architecture dependent due to the varying sizes and representations of C types. Hiding that variation behind a newtype serves only to obscure that dependency, it certainly doesn't resolve it. I know that Section 6.2 of the FFI addendum requires these exports to be abstract, but I now tend to think that this was a mistake. Manuel ___ Haskell-prime mailing list Haskell-prime@haskell.org http://www.haskell.org/mailman/listinfo/haskell-prime