9
votes

Is it possible to write a Haskell function that yields a parameterised type where the exact type parameter is hidden? I.e. something like f :: T -> (exists a. U a)? The obvious attempt:

{-# LANGUAGE ExistentialQuantification #-}

data D a = D a

data Wrap = forall a. Wrap (D a)

unwrap :: Wrap -> D a
unwrap (Wrap d) = d

fails to compile with:

Couldn't match type `a1' with `a'
  `a1' is a rigid type variable bound by
       a pattern with constructor
         Wrap :: forall a. D a -> Wrap,
       in an equation for `unwrap'
       at test.hs:8:9
  `a' is a rigid type variable bound by
      the type signature for unwrap :: Wrap -> D a at test.hs:7:11
Expected type: D a
  Actual type: D a1
In the expression: d
In an equation for `unwrap': unwrap (Wrap d) = d

I know this is a contrived example, but I'm curious if there is a way to convince GHC that I do not care for the exact type with which D is parameterised, without introducing another existential wrapper type for the result of unwrap.

To clarify, I do want type safety, but also would like to be able to apply a function dToString :: D a -> String that does not care about a (e.g. because it just extracts a String field from D) to the result of unwrap. I realise there are other ways of achieving it (e.g. defining wrapToString (Wrap d) = dToString d) but I'm more interested in whether there is a fundamental reason why such hiding under existential is not permitted.

2
I think the reason why exists a. D a is not a part of Haskell is a simple desire to avoid redundancy. This type is equivalent to forall r. (forall a. D a -> r) -> r, so one can just as well use that to represent existential values.n. 1.8e9-where's-my-share m.
@n.m. while strictly speaking it's not equivalent, I agree that the only useful thing that can be done to such existential value is application of a function forall a. D a -> r, so inability to express this ad-hoc existential makes sense -- thanks.narthi

2 Answers

13
votes

Yes, you can, but not in a straightforward way.

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE RankNTypes #-}

data D a = D a

data Wrap = forall a. Wrap (D a)

unwrap :: Wrap -> forall r. (forall a. D a -> r) -> r
unwrap (Wrap x) k = k x

test :: D a -> IO ()
test (D a) = putStrLn "Got a D something"

main = unwrap (Wrap (D 5)) test

You cannot return a D something_unknown from your function, but you can extract it and immediately pass it to another function that accepts D a, as shown.  

7
votes

Yes, you can convince GHC that you do not care for the exact type with which D is parameterised. Just, it's a horrible idea.

{-# LANGUAGE GADTs #-}

import Unsafe.Coerce

data D a = D a deriving (Show)

data Wrap where       -- this GADT is equivalent to your `ExistentialQuantification` version
   Wrap :: D a -> Wrap

unwrap :: Wrap -> D a
unwrap (Wrap (D a)) = D (unsafeCoerce a)

main = print (unwrap (Wrap $ D "bla") :: D Integer)

This is what happens when I execute that simple program:

enter image description here

and so on, until memory consumption brings down the system.

Types are important! If you circumvent the type system, you circumvent any predictability of your program (i.e. anything can happen, including thermonuclear war or the famous demons flying out of your nose).


Now, evidently you thought that types somehow work differently. In dynamic languages such as Python, and also to a degree in OO languages like Java, a type is in a sense a property that a value can have. So, (reference-) values don't just carry around the information needed to distinguish different values of a single type, but also information to distinguish different (sub-)types. That's in many senses rather inefficient – it's a major reason why Python is so slow and Java needs such a huge VM.

In Haskell, types don't exist at runtime. A function never knows what type the values have it's working with. Only because the compiler knows all about the types it will have, the function doesn't need any such knowledge – the compiler has already hard-coded it! (That is, unless you circumvent it with unsafeCoerce, which as I demonstrated is as unsafe as it sounds.)

If you do want to attach the type as a “property” to a value, you need to do it explicitly, and that's what those existential wrappers are there for. However, there are usually better ways to do it in a functional language. What's really the application you wanted this for?


Perhaps it's also helpful to recall what a signature with polymorphic result means. unwrap :: Wrap -> D a doesn't mean “the result is some D a... and the caller better don't care for the a used”. That would be the case in Java, but it would be rather useless in Haskell because there's nothing you can do with a value of unknown type.

Instead it means: for whatever type a the caller requests, this function is able to supply a suitable D a value. Of course this is tough to deliver – without extra information it's just as impossible as doing anything with a value of given unknown type. But if there are already a values in the arguments, or a is somehow constrained to a type class (e.g. fromInteger :: Num a => Integer -> a, then it's quite possible and very useful.


To obtain a String field – independent of the a parameter – you can just operate directly on the wrapped value:

data D a = D
    { dLabel :: String
    , dValue :: a
    }

data Wrap where Wrap :: D a -> Wrap

labelFromWrap :: Wrap -> String
labelFromWrap (Wrap (D l _)) = l

To write such functions on Wrap more generically (with any “ label accesor that doesn't care about a”), use Rank2-polymorphism as shown in n.m.'s answer.