Search code examples
haskelltype-conversiontype-safety

Use of 'unsafeCoerce'


In Haskell, there is a function called unsafeCoerce, that turns anything into any other type of thing. What exactly is this used for? Like, why we would you want to transform things into each other in such an "unsafe" way?

Provide an example of a way that unsafeCoerce is actually used. A link to Hackage would help. Example code in someones question would not.


Solution

  • unsafeCoerce lets you convince the type system of whatever property you like. It's thus only "safe" exactly when you can be completely certain that the property you're declaring is true. So, for instance:

    unsafeCoerce True :: Int
    

    is a violation and can lead to wonky, bad runtime behavior.

    unsafeCoerce (3 :: Int) :: Int
    

    is (obviously) fine and will not lead to runtime misbehavior.


    So what's a non-trivial use of unsafeCoerce? Let's say we've got an typeclass-bound existential type

    module MyClass ( SomethingMyClass (..), intSomething ) where
    
    class MyClass x where {}
    
    instance MyClass Int where {}
    
    data SomethingMyClass = forall a. MyClass a => SomethingMyClass a
    

    Let's also say, as noted here, that the typeclass MyClass is not exported and thus nobody else can ever create instances of it. Indeed, Int is the only thing that instantiates it and the only thing that ever will.

    Now when we pattern match to destruct a value of SomethingMyClass we'll be able to pull a "something" out from inside

    foo :: SomethingMyClass -> ...
    foo (SomethingMyClass a) =
      -- here we have a value `a` with type `exists a . MyClass a => a`
      --
      -- this is totally useless since `MyClass` doesn't even have any
      -- methods for us to use!
      ...
    

    Now, at this point, as the comment suggests, the value we've pulled out has no type information—it's been "forgotten" by the existential context. It could be absolutely anything which instantiates MyClass.

    Of course, in this very particular situation we know that the only thing implementing MyClass is Int. So our value a must actually have type Int. We could never convince the typechecker that this is true, but due to an outside proof we know that it is.

    Therefore, we can (very carefully)

    intSomething :: SomethingMyClass -> Int
    intSomething (SomethingMyClass a) = unsafeCoerce a    -- shudder!
    

    Now, hopefully I've suggested that this is a terrible, dangerous idea, but it also may give a taste of what kind of information we can take advantage of in order to know things that the typechecker cannot.

    In non-pathological situations, this is rare. Even rarer is a situation where using something we know and the typechecker doesn't isn't itself pathological. In the above example, we must be completely certain that nobody ever extends our MyClass module to instantiate more types to MyClass otherwise our use of unsafeCoerce becomes instantly unsafe.

    > instance MyClass Bool where {}
    > intSomething (SomethingMyClass True)
    6917529027658597398
    

    Looks like our compiler internals are leaking!


    A more common example where this sort of behavior might be valuable is when using newtype wrappers. It's a fairly common idea that we might wrap a type in a newtype wrapper in order to specialize its instance definitions.

    For example, Int does not have a Monoid definition because there are two natural monoids over Ints: sums and products. Instead, we use newtype wrappers to be more explicit.

    newtype Sum a = Sum { getSum :: a }
    
    instance Num a => Monoid (Sum a) where
      mempty = Sum 0
      mappend (Sum a) (Sum b) = Sum (a+b)
    

    Now, normally the compiler is pretty smart and recognizes that it can eliminate all of those Sum constructors in order to produce more efficient code. Sadly, there are times when it cannot, especially in highly polymorphic situations.

    If you (a) know that some type a is actually just a newtype-wrapped b and (b) know that the compiler is incapable of deducing this itself, then you might want to do

    unsafeCoerce (x :: a) :: b
    

    for a slight efficiency gain. This, for instance, occurs frequently in lens and is expressed in the Data.Profunctor.Unsafe module of profunctors, a dependency of lens.

    But let me again suggest that you really need to know what's going on before using unsafeCoerce like this is anything but highly unsafe.


    One final thing to compare is the "typesafe cast" available in Data.Typeable. This function looks a bit like unsafeCoerce, but with much more ceremony.

    unsafeCoerce ::                             a ->       b
    cast         :: (Typeable a, Typeable b) => a -> Maybe b
    

    Which, you might think of as being implemented using unsafeCoerce and a function typeOf :: Typeable a => a -> TypeRep where TypeRep are unforgeable, runtime tokens which reflect the type of a value. Then we have

    cast :: (Typeable a, Typeable b) => a -> Maybe b
    cast a = if (typeOf a == typeOf b) then Just b else Nothing 
      where b = unsafeCoerce a
    

    Thus, cast is able to ensure that the types of a and b really are the same at runtime, and it can decide to return Nothing if they are not. As an example:

    {-# LANGUAGE DeriveDataTypeable        #-}
    {-# LANGUAGE ExistentialQuantification #-}
    
    data A = A deriving (Show, Typeable)
    data B = B deriving (Show, Typeable)
    
    data Forget = forall a . Typeable a => Forget a
    
    getAnA :: Forget -> Maybe A
    getAnA (Forget something) = cast something
    

    which we can run as follows

    > getAnA (Forget A)
    Just A
    > getAnA (Forget B)
    Nothing
    

    So if we compare this usage of cast with unsafeCoerce we see that it can achieve some of the same functionality. In particular, it allows us to rediscover information that may have been forgotten by ExistentialQuantification. However, cast manually checks the types at runtime to ensure that they are truly the same and thus cannot be used unsafely. To do this, it demands that both the source and target types allow for runtime reflection of their types via the Typeable class.