[Haskell-cafe] Foralls in records

Matthew Brecknell haskell at brecknell.org
Wed Mar 14 02:12:43 EDT 2007


Adde:
> data TransactionT = forall c. (Connection c) => TransactionT c
> 
> data Transaction a = Transaction (TransactionT -> (a, TransactionT))
> 
> getConnection :: Transaction c
> getConnection = Transaction (\t@(TransactionT c) -> (c, t))
> 
> class Connection c where
>   connectionExecute :: c -> String -> Transaction ()
> 
> execute :: String -> Transaction ()
> execute s = connectionExecute getConnection s

I'm assuming you've read the GHC user's guide on existentially
quantified data constructors:

http://www.haskell.org/ghc/docs/latest/html/users_guide/type-extensions.html#existential-quantification

When you wrap a value in an existentially quantified data constructor,
the concrete type of the value is erased. Although the wrapped value
does have a concrete type, "the compiler" has forgotten it. This is what
the "existential" part means: you know the type exists, but that's about
all you know. In this case, you've used a context to restrict the
existential quantification, so at least you know the type conforms to
the Connection class.

Since the concrete type has been forgotten, there's no way to get it
back. You can't write a function that exposes the forgotten type, so
getConnection is basically a lost cause. When you write "getConnection
:: Transaction c", you are saying that the function is fully polymorphic
in c. In other words, you are allowing the caller of getConnection to
specify the type of connection. But you can't do that, because you
already gave the connection a concrete type when you constructed the
TransactionT. You might think you could get away with "getConnection ::
Connection c => Transaction c", but this is still too polymorphic.

So what can you do? You can pattern-match on a TransactionT, provided
you don't allow the existentially-quantified type to escape the scope of
the pattern-match. In this case, all you know is that the type conforms
to the Connection class, so you must use methods from the Connection
class within that scope to consume the quantified type.

Now, I'm a little confused by the circular dependency between
TransactionT, Transaction and the Connection class, so I'll use a
slightly different example:

> class Connection c where
>   connectionExecute :: c -> String -> IO ()

Now, you can write a corresponding function for a TransactionT (not
tested):

> transactionExecute :: TransactionT -> String -> IO ()
> transactionExecute (TransactionT c) s = connectionExecute c s

Note that the existentially-quantified type variable is not exposed in
the signature of transactionExecute, because it has been consumed by
connectionExecute within the scope of the TransactionT pattern-match.

Hope that helps.



More information about the Haskell-Cafe mailing list