I need some help and everyone’s comment is welcomed!
But first - a bit of history and why I’m asking for help and why I’m asking now.
History
Haskell language sometimes is not capable to describe a right type. And some of such types we could describe using existential quantifier (exists
or ∃
).
Most advanced Proposal was made in First-class existentials by GoldFirere (Richard Eisenberg). Unfortunately adding existential quantifier requires to add most elements from Calculus of constructions (with proofs, packing. unpacking, witnesses and so on). So, that work was frozen.
Mine idea was that it is possible to add existential quantifier (maybe not as universal as in Goldfire proposal) and to stay in System Fs.
I even wrote my Proposal Extractable Existentials with simple idea:
data Box = forall a. MkBox a
fromBox :: Box -> exists a. a
fromBox (MkBox x) = x
But I was fairly asked “So, what is your proposal to Core language?”
And I was stack! I spent almost a year for this question.
Most interesting articles for my understanding of Lambda Calculus were
A Look at Typed Lambda Calculus by Nikolay Yakimov and
Haskell to Core: Understanding Haskell Features Through Their Desugaring by Vladislav Zavialov
Right now I understand Lambda Calculus much better. But even now these judgments looks for me as tautologies “If we have x:t
then we have x:t
”.
And that’s why I need your help to find and write those judgments.
But why I’m asking right now?
Because I made a small discovery recently!
Discovery 1. Forany
I opened the article “Haskell to core” in 1001 time and looked at
data E = forall a. MkE [a]
lenE :: E -> Int
lenE (MkE xs) =
List.length xs
-- is desugaring to Core
lenE =
\(e :: E) ->
case e of
MkE @a xs ->
List.length @a xs
I surprisingly found that Haskell already has all machinery and algorithms, but has not mathematical judgments.
I used a trick not from Lambda Calculus, but from Theory of Categories, which tells us that if exists such non-primitive function, we could split it in two:
f = f1 * f2
So, let’s do it with lenE
function (just types):
lenE :: E -> Int
lenE = lenEa . unE
unE :: E -> forany a. [a]
lenEa :: forall a. [a] -> Int
Here I used forany
quantifier instead of exixsts
because this quantifier is not as universal as exists
And before we split function body we add some “hidden” expressions:
lenE =
\(e :: E) ->
case e of
MkE (createNewTyVar @(a::k) in @a) xs ->
saveTyVar @(a::k) in
loadTyVar @(a::k) in
List.length @a xs
-- split function into 2
unE =
\(e :: E) ->
case e of
MkE (createNewTyVar @(a::k) in @a) xs ->
saveTyVar @(a::k) in xs
lenEa =
\(a :: Type) ->
\(xs :: a) ->
List.length @a xs
lenE =
lenEa (loadTyVar @(a::k) in @a) . unE
Huge Suscess!!
As we see, forany
quantifier don’t require any additional type to Core types, type variable
works just fine. This means, that we add to Core types ForAny a. T
and all judgement for it are identical as for ForAll a. T
types (but we need to find additional judgement for forany
).
But it requires some additional Core expressions (saveTyVar
and loadTyVar
and hidden createNewTyVar
) and special rules for those functions, which consists forany
quantifier.
Discovery 2. Union types
Rght now forany
is just Extractable Existential. Could we make something more, but still remain in System FS? Yes we can! We can add Depended sum / Union types.
For this we need to extend Core types to TUnion t1 t2
(but for simplicity I use here TUnion [t1]
).
And we add just 1 special Core expression (caseDyn
), which is a combination of createNewTyVar
and case
expressions:
data T a where
MkInt :: T Int
MkBool :: T Bool
serialise :: T a -> String
serialise MkInt = "int"
serialise MkBool = "bool"
deserialise :: String -> forany a. T a
deserialise "int" = MkInt
deserialise "bool" = MkBool
-- alternative typing
deserialise :: String -> forany (Int | Bool). T (Int | Bool)
-- alternative typing with coerce
deserialise :: String -> forany a. a ~ (Int | Bool) => T a
And to core desugaring is looking like this:
deserialise =
\(x :: String) ->
createNewTyVarOfKind @(a:: Type) in
caseDyn x of
"int" -> setTyVar @Int in saveTyVar @a in MkInt
"bool" -> setTyVar @Bool in saveTyVar @a in MkBool
Union types works like Set
:
(a | b) ~ (b | a)
- Commutativity rule(a::k | b::k)
- Same-Kind Rule for “sub”-types(a | a) ~ a
- Self-Reduction Rule(a | Void) ~ a ~ (Void | a)
- Void-Reduction Rulea ⊆ (a | b)
- Hierarchical Rule(a | b) ⊆ ANY
- Principal type Rule (ifb !~ a
)(a !| a) ~ Void
- Self-Anihilation Rule(a !| b) ∈ ERROR
ifa, b !⊆ (c | d)
(includingANY
), anda, b !∈ TVAR
- Wrong typed type
Is ForAny as powerful as Exists?
Unfortunately, it looks like forany
quantifier is still weaker then exists
.
Let’s try to write function toVec
and filterVec
:
data Nat = Zero | Succ Nat
type Vec :: Nat -> Type -> Type
data Vec n a where
VNil :: Vec Zero a
(:>) :: a -> Vec n a -> Vec (Succ n) a
infixr 5 :>
toVec :: forall a.
[a] ->
forany (n::Nat). n ~ ( ?? | ?? ) => Vec n a
toVec [] = VNil
toVec (x:xs) = x :> toVec xs
--
filterVec :: forall a (n::Nat).
(a -> Bool) ->
Vec n a ->
forany (m::Nat). m ~ (?? | ?? | ??) => Vec m a
filterVec p VNil = VNil
filterVec p (x :> xs)
| p x = x :> filterVec p xs
| otherwise = filterVec p xs
What should we write to caseDyn
expression? setTyVar @?? in ..
?
How to typecheck? How to evaluate types?
It is unclear.
Summary
I’m asking for help not for some homework, but to add to Haskell ForAny
quantifier and still remain in System Fs.
Feedback is welcomed!