RankNTypes and PolyKinds - haskell

What is the difference between f1 and f2?
$ ghci -XRankNTypes -XPolyKinds
Prelude> let f1 = undefined :: (forall a m. m a -> Int) -> Int
Prelude> let f2 = undefined :: (forall (a :: k) m. m a -> Int) -> Int
Prelude> :t f1
f1 :: (forall (a :: k) (m :: k -> *). m a -> Int) -> Int
Prelude> :t f2
f2 :: (forall (k :: BOX) (a :: k) (m :: k -> *). m a -> Int) -> Int
Related to this question on RankNTypes and scope of forall. Example taken from the GHC user's guide on kind polymorphism.

f2 requires its argument to be polymorphic in the kind k, while f1 is just polymorphic in the kind itself. So if you define
{-# LANGUAGE RankNTypes, PolyKinds #-}
f1 = undefined :: (forall a m. m a -> Int) -> Int
f2 = undefined :: (forall (a :: k) m. m a -> Int) -> Int
x = undefined :: forall (a :: *) m. m a -> Int
then :t f1 x types fine, while :t f2 x complains:
*Main> :t f2 x
<interactive>:1:4:
Kind incompatibility when matching types:
m0 :: * -> *
m :: k -> *
Expected type: m a -> Int
Actual type: m0 a0 -> Int
In the first argument of ‘f2’, namely ‘x’
In the expression: f2 x

Let's be bloody. We must quantify everything and give the domain of quantification. Values have types; type-level things have kinds; kinds live in BOX.
f1 :: forall (k :: BOX).
(forall (a :: k) (m :: k -> *). m a -> Int)
-> Int
f2 :: (forall (k :: BOX) (a :: k) (m :: k -> *). m a -> Int)
-> Int
Now, in neither example type is k quantified explicitly, so ghc is deciding where to put that forall (k :: BOX), based on whether and where k is mentioned. I am not totally sure I understand or am willing to defend the policy as stated.
Ørjan gives a good example of the difference in practice. Let's be bloody about that, too. I'll write /\ (a :: k). t to make explicit the abstraction that corresponds to forall, and f # type for the corresponding application. The game is that we get to pick the #-ed arguments, but we have to be ready to put up with whatever /\-ed arguments the devil may choose.
We have
x :: forall (a :: *) (m :: * -> *). m a -> Int
and may accordingly discover that f1 x is really
f1 # * (/\ (a :: *) (m :: * -> *). x # a # m)
However, if we try to give f2 x the same treatment, we see
f2 (/\ (k :: BOX) (a :: k) (m :: k -> *). x # ?m0 # ?a0)
?m0 :: *
?a0 :: * -> *
where m a = m0 a0
The Haskell type system treats type application as purely syntactic, so the only way that equation can be solved is by identifying the functions and identifying the arguments
(?m0 :: * -> *) = (m :: k -> *)
(?a0 :: *) = (a :: k)
but those equations are not even well kinded, because k is not free to be chosen: it's being /\-ed not #-ed.
Generally, to get to grips with these uber-polymorphic types, it's good to write out all the quantifiers and then figure out how that turns into your game against the devil. Who chooses what, and in what order. Moving a forall inside an argument type changes its chooser, and can often make the difference between victory and defeat.

The type of f1 places more restrictions on its definition, while the type of f2 places more restrictions on its argument.
That is: the type of f1 requires its definition to be polymorphic in the kind k, while the type of f2 requires its argument to be polymorphic in the kind k.
f1 :: forall (k::BOX). (forall (a::k) (m::k->*). m a -> Int) -> Int
f2 :: (forall (k::BOX) (a::k) (m::k->*). m a -> Int) -> Int
-- Show restriction on *definition*
f1 g = g (Just True) -- NOT OK. f1 must work for all k, but this assumes k is *
f2 g = g (Just True) -- OK
-- Show restriction on *argument* (thanks to Ørjan)
x = undefined :: forall (a::*) (m::*->*). m a -> Int
f1 x -- OK
f2 x -- NOT OK. the argument for f2 must work for all k, but x only works for *

Related

Is it possible to define variadic-kinded data types?

I can define a polykinded natural transformation like so:
type family (~>) :: k -> k -> *
type instance (~>) = (->)
newtype NT a b = NT { apply :: forall x. a x ~> b x }
type instance (~>) = NT
Which works at all kinds, so I can define e.g.
left :: Either ~> (,)
left = NT (NT (Left . fst))
This is cool and inspiring. But no matter how many tricks I play, I can't seem to get something variadic in the return type. E.g. I would like
type family (:*:) :: k -> k -> k
type instance (:*:) = (,)
type instance (:*:) = ???
It seems like this is impossible, since type families need to be fully saturated, and you can only introduce type constructors in *.
I've even tried some rather nasty tricks
type instance (:*:) = Promote2 (:*:)
type family Promote2 :: (j -> k -> l) -> (a -> j) -> (a -> k) -> (a -> l) where
promote2_law :: Promote2 f x y z :~: f (x z) (y z)
promote2_law = unsafeCoerce Refl
fstP :: forall (a :: k -> *) (b :: k -> *) (c :: k). (a :*: b) c -> a c
fstP = case promote2_law #(:~:) #a #b #c of Refl -> NT (\(a,b) -> a)
And I don't know if that even has any hope of working, since I haven't thought through how higher kinded things are "represented". But GHC knows I'm lying anyway
• Couldn't match type ‘(,)’ with ‘Promote2 (,) a’
Inaccessible code in
a pattern with constructor: Refl :: forall k (a :: k). a :~: a,
Are there any other tricks for this?
The "axiomatic" approach does actually work, I had just used the equality wrong:
fstP :: forall (a :: j -> k) (b :: j -> k) (x :: j). (a :*: b) x -> a x
fstP = castWith (Refl ~% promote2_law #(:*:) #a #b #x ~% Refl) fst
where
infixl 9 ~%
(~%) = Data.Type.Equality.apply
Using Equality.apply is essential to inform the type checker of where to apply the axiom. I made a full development of higher-kinded products here for reference.
Be warned, as I was playing with this did I get a GHC panic once. So the nasty tricks might be nasty. Still interested in other approaches.

When is forall not for all

In the program below test₁ will not compile but test₂ will. The reason seems to be because of the forall s. in withModulus₁. It seems that the s is a different type for each and every call to withModulus₁ because of the forall s.. Why is that the case?
{-# LANGUAGE
GADTs
, KindSignatures
, RankNTypes
, TupleSections
, ViewPatterns #-}
module Main where
import Data.Reflection
newtype Modulus :: * -> * -> * where
Modulus :: a -> Modulus s a
deriving (Eq, Show)
newtype M :: * -> * -> * where
M :: a -> M s a
deriving (Eq, Show)
add :: Integral a => Modulus s a -> M s a -> M s a -> M s a
add (Modulus m) (M a) (M b) = M (mod (a + b) m)
mul :: Integral a => Modulus s a -> M s a -> M s a -> M s a
mul (Modulus m) (M a) (M b) = M (mod (a * b) m)
unM :: M s a -> a
unM (M a) = a
withModulus₁ :: a -> (forall s. Modulus s a -> w) -> w
withModulus₁ m k = k (Modulus m)
withModulus₂ :: a -> (Modulus s a -> w) -> w
withModulus₂ m k = k (Modulus m)
test₁ = withModulus₁ 89 (\m ->
withModulus₁ 7 (\m' ->
let
a = M 131
b = M 127
in
unM $ add m' (mul m a a) (mul m b b)))
test₂ = withModulus₂ 89 (\m ->
withModulus₂ 7 (\m' ->
let
a = M 131
b = M 127
in
unM $ add m' (mul m a a) (mul m b b)))
Here is the error message:
Modulus.hs:41:29: error:
• Couldn't match type ‘s’ with ‘s1’
‘s’ is a rigid type variable bound by
a type expected by the context:
forall s. Modulus s Integer -> Integer
at app/Modulus.hs:(35,9)-(41,52)
‘s1’ is a rigid type variable bound by
a type expected by the context:
forall s1. Modulus s1 Integer -> Integer
at app/Modulus.hs:(36,11)-(41,51)
Expected type: M s1 Integer
Actual type: M s Integer
• In the second argument of ‘add’, namely ‘(mul m a a)’
In the second argument of ‘($)’, namely
‘add m' (mul m a a) (mul m b b)’
In the expression: unM $ add m' (mul m a a) (mul m b b)
• Relevant bindings include
m' :: Modulus s1 Integer (bound at app/Modulus.hs:36:28)
m :: Modulus s Integer (bound at app/Modulus.hs:35:27)
|
41 | unM $ add m' (mul m a a) (mul m b b)))
| ^^^^^^^^^
Briefly put, a function
foo :: forall s . T s -> U s
lets its caller to choose what the type s is. Indeed, it works on all types s. By comparison,
bar :: (forall s . T s) -> U
requires that its caller provides an argument x :: forall s. T s, i.e. a polymorphic value that will work on all types s. This means that bar will choose what the type s will be.
For instance,
foo :: forall a. a -> [a]
foo x = [x,x,x]
is obvious. Instead,
bar :: (forall a. a->a) -> Bool
bar x = x 12 > length (x "hello")
is more subtle. Here, bar first uses x choosing a ~ Int for x 12, and then uses x again choosing a ~ String for x "hello".
Another example:
bar2 :: Int -> (forall a. a->a) -> Bool
bar2 n x | n > 10 = x 12 > 5
| otherwise = length (x "hello") > 7
Here a is chosen to be Int or String depending on n > 10.
Your own type
withModulus₁ :: a -> (forall s. Modulus s a -> w) -> w
states that withModulus₁ must be allowed to choose s to any type it wishes. When calling this as
withModulus₁ arg (\m -> ...)
m will have type Modulus s0 a where a was chosen by the caller, while s was chosen by withModulus₁ itself. It is required that ... must be compatible with any choice withModulus₁ may take.
What if we nest calls?
withModulus₁ arg (\m1 -> ...
withModulus₁ arg (\m2 -> ...)
...
)
Now, m1 :: Modulus s0 a as before. Further m2 :: Modulus s1 a where s1 is chosen by the innermost call to withModulus₁.
The crucial point, here, is that there is no guarantee that s0 is chosen to be the same as s1. Each call might make a different choice: see e.g. bar2 above which indeed does so.
Hence, the compiler can not assume that s0 and s1 are equal. Hence, if we call a function that requires their equality, like add, we get a type error, since this would constrain the freedom of choice of s by the two withModulus₁ calls.

How can i get the type of a polymorphic function for a specific type class instance?

For example, typing :t ap in GHCi gives the result
ap :: Monad m => m (a -> b) -> m a -> m b
If I already know the Monad instance I'm going to use is ((->) r), how can I query for the type of ap for that specific instance?
As Lazersmoke said as a comment you can use the TypeApplications extension that was introduced in GHC 8.0.
In GHCi:
λ > :set -XTypeApplications
λ > import Control.Monad
λ > :t ap #((->) _)
ap #((->) _) :: (t -> a -> b) -> (t -> a) -> t -> b
You can use visible type application feature to specify parametric types. You can look at functions in more creative way: functions in Haskell can be applied to not only values of some types, but also to types of that values. But to pass type you should somehow specify (with prepending #) that you're passing types (because types are not first-class objects in Haskell yet).
So here how it works:
λ: :set -XTypeApplications
λ: :t ap #((->) Int)
ap #((->) Int) :: (Int -> a -> b) -> (Int -> a) -> Int -> b
The only limitation of such approach is that you can't use type variables in ghci, you should use specific types (Int instead of r) but this is not big deal.
ADVANCED SECTION
Well, actually you can, but it's tricky:
λ: :set -XExplicitForAll
λ: :set -XPartialTypeSignatures
λ: :set -XScopedTypeVariables
λ: :{
λ| foo :: forall r . _
λ| foo = ap #((->) r)
λ| :}
<interactive>:28:19: warning: [-Wpartial-type-signatures]
• Found type wildcard ‘_’
standing for ‘(r -> a -> b) -> (r -> a) -> r -> b’
λ: :t foo
foo :: (r -> a -> b) -> (r -> a) -> r -> b
UPD: You can actually use placeholders instead of type variables (see another answer). But if you want to specify exact names use approach from above.
λ: :t ap #((->) _)
ap #((->) _) :: (t -> a -> b) -> (t -> a) -> t -> b
/ADVANCED SECTION
One more thing to say about this approach: you should do something more if your functions have several type parameters and you want to specify exact one. Types are passed one by one from left to right just as simple arguments in some function like bar :: Int -> String -> Double. If you want to fix first argument of bar you should write bar 5 and if you want to fix second, then, well, you can write something like \n -> bar n "baz" but this doesn't work with type application. You need to know two things:
Order of types.
How to specify desired type.
Consider next function:
λ: :t lift
lift :: (Monad m, MonadTrans t) => m a -> t m a
We want be able to specify m and t type variables. Because Haskell has no named type variables (yet) you can't write :t lift {t=MaybeT} or :t lift {m=IO} unfortunately. So go back to two things.
To see order of types you should use some compiler options. Order of type arguments is specified by forall and you can do it manually. Otherwise type parameters will be sorted somehow by the compiler. Mere mortals can't see order of types for lift function but if you're aware of some high-level magic you can:
λ: :set -fprint-explicit-foralls
λ: :t lift
lift
:: forall {t :: (* -> *) -> * -> *} {a} {m :: * -> *}.
(Monad m, MonadTrans t) =>
m a -> t m a
And then you should use #_ to skip some types:
λ: :t lift #MaybeT
lift #MaybeT
:: forall {a} {m :: * -> *}. Monad m => m a -> MaybeT m a
λ: :t lift #_ #IO
lift #_ #IO
:: forall {t :: (* -> *) -> * -> *} {a}.
MonadTrans t =>
IO a -> t IO a
λ: :t lift #_ #_ #Int
lift #_ #_ #Int
:: forall {t :: (* -> *) -> * -> *} {t1 :: * -> *}.
(Monad t1, MonadTrans t) =>
t1 Int -> t t1 Int
Well, this is really mystery for me why m is shown as third argument in forall but should be passed as second but I'm still not aware of all magic.
This is just a hack, but you could always do something like:
:t ap . (id :: ((->) r a) -> ((->) r a))
or
:t \x y -> (id :: ...) (ap x y)
interestingly
Prelude Control.Monad> type Reader = (->) r
Prelude Control.Monad> :t ap . (id :: Reader r a -> Reader r a)
ap . (id :: Reader r a -> Reader r a)
:: Reader r (a -> b) -> (r -> a) -> r -> b
differs from
Prelude Control.Monad> :t \x y -> (id :: Reader r a -> Reader r a) (ap x y)
\x y -> (id :: Reader r a -> Reader r a) (ap x y)
:: (r -> a1 -> a) -> (r -> a1) -> Reader r a
in what ghc recognizes as the synonym Reader r a

Is polykinded type application injective?

Is polykinded type application injective?
When we enable PolyKinds, do we know that f a ~ g b implies f ~ g and a ~ b?
Motivation
When trying to answer another question, I reduced the problem to the point that I received the following error only with PolyKinds enabled.
Could not deduce (c1 ~ c)
from the context ((a, c z) ~ (c1 a1, c1 b))
If polykinded type application were injective, we could deduce c1 ~ c as follows.
(a, c z) ~ (c1 a1, c1 b)
(a,) (c z) ~ (c1 a1,) (c1 b) {- switch to prefix notation -}
c z ~ c1 b {- f a ~ g b implies a ~ b -}
c ~ c1 {- f a ~ g b implies f ~ g -}
c1 ~ c {- ~ is reflexive -}
Type application is injective
In Haskell, type application is injective. If f a ~ g b then f ~ g and a ~ b. We can prove this to ourselves by compiling the following
{-# LANGUAGE GADTs #-}
import Control.Applicative
second :: a -> a -> a
second _ = id
typeApplicationIsInjective :: (Applicative f, f a ~ g b) => f a -> g b -> f b
typeApplicationIsInjective fa gb = second <$> fa <*> gb
Kind of type application is not injective
The kind of a type application is not injective. If we consider the following, which has kind (* -> *) -> *.
newtype HoldingInt f = HoldingInt (f Int)
We can ask ghci what kind something of kind (* -> *) -> * has when applied to something of kind * -> *, which is *
> :k HoldingInt
HoldingInt :: (* -> *) -> *
> :k Maybe
Maybe :: * -> *
> :k HoldingInt Maybe
HoldingInt Maybe :: *
This is the same kind as something of kind * -> * applied to something of kind *
> :k Maybe
Maybe :: * -> *
> :k Int
Int :: *
> :k Maybe Int
Maybe Int :: *
Therefore, it is not true that, borrowing syntax from KindSignatures, the first set of kind signatures implies anything in the second.
f :: kf, g :: kg, a :: ka, b :: kb, f a :: k, g b :: k
g :: kf, f :: kg, b :: ka, a :: kb
Polykinded type application is injective from the outside, but certainly not injective from inside Haskell.
By "injective from the outside" I mean that whenever there is a Refl with type f a :~: g b, then it must be the case that f is equal to g and a is equal to b, and since we know that types of different kinds are never equal, the kinds must be also the same.
The issue is that GHC only has homogeneous type equality constraints, and doesn't have kind equality constraints at all. The machinery for encoding GADTs using coercions exists only on the type and promoted type level. That's why we can't express heterogeneous equality, and why we can't promote GADTs.
{-# LANGUAGE PolyKinds, GADTs, TypeOperators #-}
data HEq (a :: i) (b :: k) :: * where
HRefl :: HEq a a
-- ERROR: Data constructor ‘HRefl’ cannot be GADT-like in its *kind* arguments
Also, here's a simple example of GHC not inferring injectivity:
sym1 :: forall f g a b. f a :~: g b -> g b :~: f a
sym1 Refl = Refl
-- ERROR: could not deduce (g ~ f), could not deduce (b ~ a)
If we annotate a and b with the same kind, it checks out.
This paper talks about the above limitations and how they could be eliminated in GHC (they describe a system with unified kind/type coercions and heterogeneous equality constraints).
If a type-level application has different kinds, then the two types can not be shown to be equal. Here is evidence:
GHC.Prim> () :: ((Any :: * -> *) Any) ~ ((Any :: (* -> *) -> *) Any) => ()
<interactive>:6:1:
Couldn't match kind ‘*’ with ‘* -> *’
Expected type: Any Any
Actual type: Any Any
In the expression:
() :: ((Any :: * -> *) Any) ~ ((Any :: (* -> *) -> *) Any) => ()
In an equation for ‘it’:
it
= () :: ((Any :: * -> *) Any) ~ ((Any :: (* -> *) -> *) Any) => ()
<interactive>:6:7:
Couldn't match kind ‘*’ with ‘* -> *’
Expected type: Any Any
Actual type: Any Any
In the ambiguity check for: Any Any ~ Any Any => ()
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
In an expression type signature:
((Any :: * -> *) Any) ~ ((Any :: (* -> *) -> *) Any) => ()
In the expression:
() :: ((Any :: * -> *) Any) ~ ((Any :: (* -> *) -> *) Any) => ()
(Even turning on the suggested AllowAmbiguousTypes extension gives the same type-checking error -- just without the suggestion.)
Therefore, if two types can be shown to be equal, then type-level applications in the same structural position on the two sides of the equality have the same kind.
If you wish for proof instead of evidence, one would need to write down a careful inductive proof about the system described in Type Checking with Open Type Functions. Inspection of Figure 3 suggests to me that the invariant, "all type applications in ~'s have the same kind on both sides of the ~" is preserved, though neither I nor the paper prove this carefully, so there is some chance it is not so.

How to express existential types using higher rank (rank-N) type polymorphism?

We're used to having universally quantified types for polymorphic functions. Existentially quantified types are used much less often. How can we express existentially quantified types using universal type quantifiers?
It turns out that existential types are just a special case of Σ-types (sigma types). What are they?
Sigma types
Just as Π-types (pi types) generalise our ordinary function types, allowing the resulting type to depend on the value of its argument, Σ-types generalise pairs, allowing the type of second component to depend on the value of the first one.
In a made-up Haskell-like syntax, Σ-type would look like this:
data Sigma (a :: *) (b :: a -> *)
= SigmaIntro
{ fst :: a
, snd :: b fst
}
-- special case is a non-dependent pair
type Pair a b = Sigma a (\_ -> b)
Assuming * :: * (i.e. the inconsistent Set : Set), we can define exists a. a as:
Sigma * (\a -> a)
The first component is a type and the second one is a value of that type. Some examples:
foo, bar :: Sigma * (\a -> a)
foo = SigmaIntro Int 4
bar = SigmaIntro Char 'a'
exists a. a is fairly useless - we have no idea what type is inside, so the only operations that can work with it are type-agnostic functions such as id or const. Let's extend it to exists a. F a or even exists a. Show a => F a. Given F :: * -> *, the first case is:
Sigma * F -- or Sigma * (\a -> F a)
The second one is a bit trickier. We cannot just take a Show a type class instance and put it somewhere inside. However, if we are given a Show a dictionary (of type ShowDictionary a), we can pack it with the actual value:
Sigma * (\a -> (ShowDictionary a, F a))
-- inside is a pair of "F a" and "Show a" dictionary
This is a bit inconvenient to work with and assumes that we have a Show dictionary around, but it works. Packing the dictionary along is actually what GHC does when compiling existential types, so we could define a shortcut to have it more convenient, but that's another story. As we will learn soon enough, the encoding doesn't actually suffer from this problem.
Digression: thanks to constraint kinds, it's possible to reify the type class into concrete data type. First, we need some language pragmas and one import:
{-# LANGUAGE ConstraintKinds, GADTs, KindSignatures #-}
import GHC.Exts -- for Constraint
GADTs already give us the option to pack a type class along with the constructor, for example:
data BST a where
Nil :: BST a
Node :: Ord a => a -> BST a -> BST a -> BST a
However, we can go one step further:
data Dict :: Constraint -> * where
D :: ctx => Dict ctx
It works much like the BST example above: pattern matching on D :: Dict ctx gives us access to the whole context ctx:
show' :: Dict (Show a) -> a -> String
show' D = show
(.+) :: Dict (Num a) -> a -> a -> a
(.+) D = (+)
We also get quite natural generalisation for existential types that quantify over more type variables, such as exists a b. F a b.
Sigma * (\a -> Sigma * (\b -> F a b))
-- or we could use Sigma just once
Sigma (*, *) (\(a, b) -> F a b)
-- though this looks a bit strange
The encoding
Now, the question is: can we encode Σ-types with just Π-types? If yes, then the existential type encoding is just a special case. In all glory, I present you the actual encoding:
newtype SigmaEncoded (a :: *) (b :: a -> *)
= SigmaEncoded (forall r. ((x :: a) -> b x -> r) -> r)
There are some interesting parallels. Since dependent pairs represent existential quantification and from classical logic we know that:
(∃x)R(x) ⇔ ¬(∀x)¬R(x) ⇔ (∀x)(R(x) → ⊥) → ⊥
forall r. r is almost ⊥, so with a bit of rewriting we get:
(∀x)(R(x) → r) → r
And finally, representing universal quantification as a dependent function:
forall r. ((x :: a) -> R x -> r) -> r
Also, let's take a look at the type of Church-encoded pairs. We get a very similar looking type:
Pair a b ~ forall r. (a -> b -> r) -> r
We just have to express the fact that b may depend on the value of a, which we can do by using dependent function. And again, we get the same type.
The corresponding encoding/decoding functions are:
encode :: Sigma a b -> SigmaEncoded a b
encode (SigmaIntro a b) = SigmaEncoded (\f -> f a b)
decode :: SigmaEncoded a b -> Sigma a b
decode (SigmaEncoded f) = f SigmaIntro
-- recall that SigmaIntro is a constructor
The special case actually simplifies things enough that it becomes expressible in Haskell, let's take a look:
newtype ExistsEncoded (F :: * -> *)
= ExistsEncoded (forall r. ((x :: *) -> (ShowDictionary x, F x) -> r) -> r)
-- simplify a bit
= ExistsEncoded (forall r. (forall x. (ShowDictionary x, F x) -> r) -> r)
-- curry (ShowDictionary x, F x) -> r
= ExistsEncoded (forall r. (forall x. ShowDictionary x -> F x -> r) -> r)
-- and use the actual type class
= ExistsEncoded (forall r. (forall x. Show x => F x -> r) -> r)
Note that we can view f :: (x :: *) -> x -> x as f :: forall x. x -> x. That is, a function with extra * argument behaves as a polymorphic function.
And some examples:
showEx :: ExistsEncoded [] -> String
showEx (ExistsEncoded f) = f show
someList :: ExistsEncoded []
someList = ExistsEncoded $ \f -> f [1]
showEx someList == "[1]"
Notice that someList is actually constructed via encode, but we dropped the a argument. That's because Haskell will infer what x in the forall x. part you actually mean.
From Π to Σ?
Strangely enough (although out of the scope of this question), you can encode Π-types via Σ-types and regular function types:
newtype PiEncoded (a :: *) (b :: a -> *)
= PiEncoded (forall r. Sigma a (\x -> b x -> r) -> r)
-- \x -> is lambda introduction, b x -> r is a function type
-- a bit confusing, I know
encode :: ((x :: a) -> b x) -> PiEncoded a b
encode f = PiEncoded $ \sigma -> case sigma of
SigmaIntro a bToR -> bToR (f a)
decode :: PiEncoded a b -> (x :: a) -> b x
decode (PiEncoded f) x = f (SigmaIntro x (\b -> b))
I found an anwer in Proofs and Types by Jean-Yves Girard, Yves Lafont and Paul Taylor.
Imagine we have some one-argument type t :: * -> * and construct an existential type that holds t a for some a: exists a. t a. What can we do with such a type? In order to compute something out of it we need a function that can accept t a for arbitrary a, that means a function of type forall a. t a -> b. Knowing this, we can encode an existential type simply as a function that takes functions of type forall a. t a -> b, supplies the existential value to them and returns the result b:
{-# LANGUAGE RankNTypes #-}
newtype Exists t = Exists (forall b. (forall a. t a -> b) -> b)
Creating an existential value is now easy:
exists :: t a -> Exists t
exists x = Exists (\f -> f x)
And if we want to unpack the existential value, we just apply its content to a function that produces the result:
unexists :: (forall a. t a -> b) -> Exists t -> b
unexists f (Exists e) = e f
However, purely existential types are of very little use. We cannot do anything reasonable with a value we know nothing about. More often we need an existential type with a type class constraint. The procedure is just the same, we just add a type class constraint for a. For example:
newtype ExistsShow t = ExistsShow (forall b. (forall a. Show a => t a -> b) -> b)
existsShow :: Show a => t a -> ExistsShow t
existsShow x = ExistsShow (\f -> f x)
unexistsShow :: (forall a. Show a => t a -> b) -> ExistsShow t -> b
unexistsShow f (ExistsShow e) = e f
Note: Using existential quantification in functional programs is often considered a code-smell. It can indicate that we haven't liberated ourselves from OO thinking.

Resources