Skip to the content.

Playing with Sigma, part 1

As I wrote in the previous post, you can think Sigma is a generalized existential type. Then, how can I build it?

First, let’s start from SomeX that contains type X indexed by kind S.

{-# LANGUAGE DataKinds,
             GADTs,
             PolyKinds,
             TemplateHaskell,
             TypeFamilies,
             TypeOperators
#-}
{-# OPTIONS_GHC -Wincomplete-patterns #-}

import Data.Kind (Type)
import Data.Singletons.TH

singletons [d|
    data S = S1 | S2 | S3 | S4
  |]

data X (s :: S) = X

data SomeX where
    SomeX :: Sing s -> X s -> SomeX

Next, generialize it so that it can have any type indexed by S.

data Some1 (t :: S -> Type) where
    Some1 :: Sing s -> t s -> Some1 t

Okay, the indexing type isn’t necessarily S. Let’s allow any kind k.

data Some2 (t :: k -> Type) where
    Some2 :: Sing s -> t s -> Some2 t

Now k is inferred by the compiler, but let’s specify it explicitly. Just like a data constructor can take a type now (as a singleton), a type constructor can now take a kind.

data Some3 k (t :: k -> Type) where
    Some3 :: Sing s -> t s -> Some3 k t

What kind will it be when you specify a kind explicitly? GHCi tells it’s forall k -> (k -> *) -> *.

> :k Some3
Some3 :: forall k -> (k -> *) -> *

This forall means it’s a visible kind. You cannot write this kind signature in the code in GHC 8.8, but you can write it using StandaloneKindSignatures extention in GHC 8.10.

Okay, next, make it use defunctionalized symbols so that you can pass a partially-applied type function to it.

data Some4 k (t :: k ~> Type) where
    Some4 :: Sing s -> t @@ s -> Some4 k t

Now, you’ll find this Some4 is equivalent to Sigma.