Search code examples
haskellexistential-type

Haskell function returning existential type


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.


Solution

  • 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.