Can I coerce type constructors to Any?
The documentation for Any says:
The type constructor Any is type to which you can unsafely coerce any lifted type, and back. More concretely, for a lifted type t and value x :: t, unsafeCoerce (unsafeCoerce x :: Any) :: t is equivalent to x.
https://www.stackage.org/haddock/lts-24.28/base-4.20.2.0/GHC-Base.html#t:Any Is this also true of type constructors? For example, could I coerce `f x` to `Any x`? (assuming f maps lifted types to lifted types) In particular I'm interested in things like data C f where MkC :: (forall a. Monad (f a)) => C newtype CD f = MkCD (C Any) putCD :: C f -> CD f putCD c = MkCD (unsafeCoerce c) getCD :: CD f -> C f getCD (MkCD cd) = unsafeCoerce cd Is that sound? Thanks, Tom
On Wed, Jan 21, 2026 at 11:14:49PM +0000, Tom Ellis wrote:
could I coerce `f x` to `Any x`? (assuming f maps lifted types to lifted types)
Coincidentally, Andreas Klebinger posted something today on a ghc-proposals discussion:
It's also worth pointing out that GHC gives no guarantees that unsafeCoerce is safe for anything beyond casting from/to `Any`
Maybe that's evidence that casting from/to `Any x` is *not* OK? (I don't think what Andreas said can *literally* be correct, because surely it's safe to unsafeCoerce from a to b when `Coercible a b` is in scope?) Tom
(I don't think what Andreas said can *literally* be correct, because surely it's safe to unsafeCoerce from a to b when `Coercible a b` is in scope?)
Yes I was perhaps a bit too restrictive. The docs in fact explicitly mention this case among some others. I don't see how it could go wrong here. In fact the docs mention this case in particular! (https://hackage-content.haskell.org/package/base-4.22.0.0/docs/Unsafe-Coerce...):
(superseded) Casting between two types which have exactly the same structure: between a newtype of T and T, or between types which differ only in "phantom" type parameters. It is now preferred to use |coerce https://hackage-content.haskell.org/package/base-4.22.0.0/docs/Data-Coerce.h...| from |Data.Coerce|, which is type-safe.
But really there is no reason to use dirty tricks like unsafeCoerce in this situation to begin with when `coerce` is right there! ----------------- As for the question about `Any x` or `f Any` I'm not sure. The basic idea of unsafeCoerce is really to be able to pass along the underlying pointer using `Any` as long as we don't use it without turning it back into it's original type first. Trying to use `Any` for type level trickery seems suspicious to me, and I would not be surprised to run into GHC bugs or worse using it that way. The concrete example sadly doesn't seem to compile. And type level edge cases are really not my speciality so maybe someone else can make more definite statements there.
On Thu, Jan 22, 2026 at 11:20:28PM +0100, Andreas Klebinger via Haskell-Cafe wrote:
(I don't think what Andreas said can *literally* be correct, because surely it's safe to unsafeCoerce from a to b when `Coercible a b` is in scope?)
Yes I was perhaps a bit too restrictive. The docs in fact explicitly mention this case among some others. I don't see how it could go wrong here. In fact the docs mention this case in particular! (https://hackage-content.haskell.org/package/base-4.22.0.0/docs/Unsafe-Coerce...):
(superseded) Casting between two types which have exactly the same structure: between a newtype of T and T, or between types which differ only in "phantom" type parameters. It is now preferred to use |coerce https://hackage-content.haskell.org/package/base-4.22.0.0/docs/Data-Coerce.h...| from |Data.Coerce|, which is type-safe.
But really there is no reason to use dirty tricks like unsafeCoerce in this situation to begin with when `coerce` is right there!
Yes, agreed. My objection was a bit of a technicality, but since I'm in unfamiliar and dangerous territory technicalities are all I have to hold on to.
-----------------
The concrete example sadly doesn't seem to compile.
Ah, you're right. Here's a version which does compile. {-# LANGUAGE QuantifiedConstraints #-} import GHC.Exts (Any) import Data.Kind (Type) import Unsafe.Coerce (unsafeCoerce) data C (f :: Type -> Type -> Type) where MkC :: (forall a. Monad (f a)) => C f newtype CD (f :: Type -> Type -> Type) = MkCD (C Any) putCD :: C f -> CD f putCD c = MkCD (unsafeCoerce c) getCD :: CD f -> C f getCD (MkCD cd) = unsafeCoerce cd
And type level edge cases are really not my speciality so maybe someone else can make more definite statements there.
OK, thanks very much for taking a look! Tom
participants (2)
-
Andreas Klebinger -
Tom Ellis