Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>It turns out that existential types are just a special case of Σ-types (sigma types). What are they?</p> <h2>Sigma types</h2> <p>Just as Π-types (pi types) generalise our ordinary function types, allowing the resulting type to <em>depend</em> on the value of its argument, Σ-types generalise pairs, allowing the type of second component to <em>depend</em> on the value of the first one.</p> <p>In a made-up Haskell-like syntax, Σ-type would look like this:</p> <pre><code>data Sigma (a :: *) (b :: a -&gt; *) = SigmaIntro { fst :: a , snd :: b fst } -- special case is a non-dependent pair type Pair a b = Sigma a (\_ -&gt; b) </code></pre> <p>Assuming <code>* :: *</code> (i.e. the inconsistent <code>Set : Set</code>), we can define <code>exists a. a</code> as:</p> <pre><code>Sigma * (\a -&gt; a) </code></pre> <p>The first component is a <em>type</em> and the second one is a value of that type. Some examples:</p> <pre><code>foo, bar :: Sigma * (\a -&gt; a) foo = SigmaIntro Int 4 bar = SigmaIntro Char 'a' </code></pre> <p><code>exists a. a</code> 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 <code>id</code> or <code>const</code>. Let's extend it to <code>exists a. F a</code> or even <code>exists a. Show a =&gt; F a</code>. Given <code>F :: * -&gt; *</code>, the first case is:</p> <pre><code>Sigma * F -- or Sigma * (\a -&gt; F a) </code></pre> <p>The second one is a bit trickier. We cannot just take a <code>Show a</code> type class instance and put it somewhere inside. However, if we are given a <code>Show a</code> dictionary (of type <code>ShowDictionary a</code>), we can pack it with the actual value:</p> <pre><code>Sigma * (\a -&gt; (ShowDictionary a, F a)) -- inside is a pair of "F a" and "Show a" dictionary </code></pre> <p>This is a bit inconvenient to work with and assumes that we have a <code>Show</code> 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.</p> <hr> <p>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:</p> <pre><code>{-# LANGUAGE ConstraintKinds, GADTs, KindSignatures #-} import GHC.Exts -- for Constraint </code></pre> <p>GADTs already give us the option to pack a type class along with the constructor, for example:</p> <pre><code>data BST a where Nil :: BST a Node :: Ord a =&gt; a -&gt; BST a -&gt; BST a -&gt; BST a </code></pre> <p>However, we can go one step further:</p> <pre><code>data Dict :: Constraint -&gt; * where D :: ctx =&gt; Dict ctx </code></pre> <p>It works much like the <code>BST</code> example above: pattern matching on <code>D :: Dict ctx</code> gives us access to the whole context <code>ctx</code>:</p> <pre><code>show' :: Dict (Show a) -&gt; a -&gt; String show' D = show (.+) :: Dict (Num a) -&gt; a -&gt; a -&gt; a (.+) D = (+) </code></pre> <hr> <p>We also get quite natural generalisation for existential types that quantify over more type variables, such as <code>exists a b. F a b</code>.</p> <pre><code>Sigma * (\a -&gt; Sigma * (\b -&gt; F a b)) -- or we could use Sigma just once Sigma (*, *) (\(a, b) -&gt; F a b) -- though this looks a bit strange </code></pre> <h2>The encoding</h2> <p>Now, the question is: can we <em>encode</em> Σ-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:</p> <pre><code>newtype SigmaEncoded (a :: *) (b :: a -&gt; *) = SigmaEncoded (forall r. ((x :: a) -&gt; b x -&gt; r) -&gt; r) </code></pre> <p>There are some interesting parallels. Since dependent pairs represent existential quantification and from classical logic we know that:</p> <pre><code>(∃x)R(x) ⇔ ¬(∀x)¬R(x) ⇔ (∀x)(R(x) → ⊥) → ⊥ </code></pre> <p><code>forall r. r</code> is <em>almost</em> <code>⊥</code>, so with a bit of rewriting we get:</p> <pre><code>(∀x)(R(x) → r) → r </code></pre> <p>And finally, representing universal quantification as a dependent function:</p> <pre><code>forall r. ((x :: a) -&gt; R x -&gt; r) -&gt; r </code></pre> <p>Also, let's take a look at the type of Church-encoded pairs. We get a very similar looking type:</p> <pre><code>Pair a b ~ forall r. (a -&gt; b -&gt; r) -&gt; r </code></pre> <p>We just have to express the fact that <code>b</code> may depend on the value of <code>a</code>, which we can do by using dependent function. And again, we get the same type.</p> <p>The corresponding encoding/decoding functions are:</p> <pre><code>encode :: Sigma a b -&gt; SigmaEncoded a b encode (SigmaIntro a b) = SigmaEncoded (\f -&gt; f a b) decode :: SigmaEncoded a b -&gt; Sigma a b decode (SigmaEncoded f) = f SigmaIntro -- recall that SigmaIntro is a constructor </code></pre> <p>The special case actually simplifies things enough that it becomes expressible in Haskell, let's take a look:</p> <pre><code>newtype ExistsEncoded (F :: * -&gt; *) = ExistsEncoded (forall r. ((x :: *) -&gt; (ShowDictionary x, F x) -&gt; r) -&gt; r) -- simplify a bit = ExistsEncoded (forall r. (forall x. (ShowDictionary x, F x) -&gt; r) -&gt; r) -- curry (ShowDictionary x, F x) -&gt; r = ExistsEncoded (forall r. (forall x. ShowDictionary x -&gt; F x -&gt; r) -&gt; r) -- and use the actual type class = ExistsEncoded (forall r. (forall x. Show x =&gt; F x -&gt; r) -&gt; r) </code></pre> <p>Note that we can view <code>f :: (x :: *) -&gt; x -&gt; x</code> as <code>f :: forall x. x -&gt; x</code>. That is, a function with extra <code>*</code> argument behaves as a polymorphic function.</p> <p>And some examples:</p> <pre><code>showEx :: ExistsEncoded [] -&gt; String showEx (ExistsEncoded f) = f show someList :: ExistsEncoded [] someList = ExistsEncoded $ \f -&gt; f [1] showEx someList == "[1]" </code></pre> <p>Notice that <code>someList</code> is actually constructed via <code>encode</code>, but we dropped the <code>a</code> argument. That's because Haskell will infer what <code>x</code> in the <code>forall x.</code> part you actually mean.</p> <h2>From Π to Σ?</h2> <p>Strangely enough (although out of the scope of this question), you can encode Π-types via Σ-types and regular function types:</p> <pre><code>newtype PiEncoded (a :: *) (b :: a -&gt; *) = PiEncoded (forall r. Sigma a (\x -&gt; b x -&gt; r) -&gt; r) -- \x -&gt; is lambda introduction, b x -&gt; r is a function type -- a bit confusing, I know encode :: ((x :: a) -&gt; b x) -&gt; PiEncoded a b encode f = PiEncoded $ \sigma -&gt; case sigma of SigmaIntro a bToR -&gt; bToR (f a) decode :: PiEncoded a b -&gt; (x :: a) -&gt; b x decode (PiEncoded f) x = f (SigmaIntro x (\b -&gt; b)) </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload