chaource: (Default)
[personal profile] chaource
Types with universal quantifiers correspond to "generic functions", that is, functions with a type parameter (or with several type parameters). For example (Scala):

def f[A]: (A, Option[A]) => Option[(A, A)] = {
case (x, oy) => oy.map(y => (x, y))
}

In a more mathematical type notation, the function f has type ∀A. A × (1 + A) → 1 + A × A.

The programmer's intuition for generic functions is that these functions work in the same way for all types. Indeed, the code of f does not depend on the type A and works in the same way for all A.

The requirement of "working in the same way for all types" can be formulated more rigorously: the code can only use a small number of constructions, but may have no side effects, no fixed values of fixed types, no run-time type information, and no calls to external libraries.

For code of that kind, the parametricity theorems, the Yoneda lemma, and the Curry-Howard correspondence hold and can be used for reasoning about the universally quantified types. In many cases, a universally quantified type may be simplified into a type that has no quantifiers.

Here are some examples of simplifying types:

∀A. A = 0 (the void type)
∀A. A → A = 1 (the unit type)
∀A. F(A) = F(0) if F is a covariant functor and F(1) if F is a contravariant functor.
∀A. (R → A) → A -- equivalent to R (here R is a fixed type, i.e., a free type variable)

All these examples follow from the Yoneda lemma.

∀A. (A → A) → (A → A) is equivalent to Church-encoded natural numbers.

This follows from the general theorem:

∀A. (F(A) → A) → A is the least fixed point of F if F is a covariant functor.

My main interest is to discover the techniques required for reasoning about those types and for simplifying to types without quantifiers. Most practically relevant examples can be solved with the Yoneda lemma. Recently I've been working on examples where the Yoneda lemma does not apply. Here are other techniques I found so far.

1) ∀A. F(A) = 0 when F(0) = 0
2) ∀A. F(A) × G(A) = (∀A. F(A) ) × (∀B. G(B) )
3) ∀A. F(A) + G(A) = (∀A. F(A) ) + (∀B. G(B) )
4) For a certain class of F(A) we have ∀A. F(A) → G(A) + H(A) = (∀A. F(A) → G(A) ) + (∀B. F(B) → H(B) )
5) For a certain class of F(A) we have ∀A. F(A) → G(A) = G(0) if G is a covariant functor.
6) For a certain class of F(A) we have ∀A. F(A) → P = P if P is a fixed type. This is proved via the relational parametricity theorem.
7) For a certain class of F(A) we have ∀A. (F(A) → P) → Q = P → Q if P, Q are fixed types. This is proved via the relational parametricity theorem.
8) If F(A) has no fixed types then ∀A. F(A) can be sometimes solved via the Curry-Howard correspondence. Each inequivalent proof of the constructive-logical formula ∀A. F(A) corresponds to a distinct value of type ∀A. F(A). If there is no proof, ∀A. F(A) = 0, and if there is only one proof, ∀A. F(A) = 1.

More examples where these techniques are used: (here P, R, etc. are fixed, given types, i.e., free type variables)

T1 = ∀A. (A → R) × A = 0 -- Techniques 1 and 2
T2 = ∀A. (A → A) → F(A) = F(0) if F is a covariant functor -- Technique 5
T3 = ∀A. (A → A) → R = R -- Technique 6
T4 = ∀A. (A → R) → A = 0 -- Yoneda
T5 = ∀A. ((A → R) → A) → R = R -- Yoneda
T6 = ∀A. ((A → R) → A) → A = ??? Nothing works so far.
T7 = ∀A. ((A → A) → A) → A = 1 -- Curry-Howard correspondence gives 1 proof
T8 = ∀A. ((A → A) → R) → S = R → S -- Technique 7
T9 = ∀A. ((A → P) → Q) → ((A → R) → S) = ((R → P) → Q) → S -- Yoneda
T10 = ∀A. (((A → P) → Q) → R) → (((A → S) → T) → U) = ??? Nothing works so far.
T11 = ∀A. (R → 1 + A) → 1 + A = ??? Nothing works so far.
T12 = ∀A. ((A → A) → A) → R = R -- Technique 6


The techniques for reasoning about those types are not simple but also not "deep" enough to be of interest to researchers. I'm still trying to figure out how to prove anything about the types I marked with "???".

Date: 2022-11-19 05:33 pm (UTC)
juan_gandhi: (Default)
From: [personal profile] juan_gandhi

This is beautiful. And a great idea in general, to dive into details.

Date: 2022-11-19 06:08 pm (UTC)
perdakot: (Default)
From: [personal profile] perdakot
and no calls to external libraries

https://en.cppreference.com/w/cpp/language/adl

Date: 2022-11-19 08:03 pm (UTC)
From: [personal profile] sassa_nf
> 2) ∀A. F(A) × G(A) = (∀A. F(A) ) × (∀B. G(B) )

Eh?... how come?

Date: 2022-11-19 08:10 pm (UTC)
From: [personal profile] sassa_nf
Oh, ok. If it were expressed like dependent types, it becomes apparent.

∀ A. X is really (A : Type) → X

So ∀A. F(A) × G(A) = (∀A. F(A) ) × (∀B. G(B) ) is really

(A: Type) → F(A) × G(A) = ((A: Type) → F(A) ) × ((B: Type) → G(B) )

Which is obviously true.

Date: 2022-11-21 06:57 am (UTC)
From: [personal profile] sassa_nf
Right, but then you didn't introduce universes, so you can't see in which universe each statement is - and that you can't declare things equal when they are from different universes.

This is more apparent with (2). What you mean there, is not (A : Set) → Either (F A) (G A), but (A : Set) → Set, whose implementation is λ A → Either (F A) (G A). Note the whole thing ((A : Set) → Set) is in Set 1, but its Either combines two types from Set 0.

Now, look what's on the right of the claimed equality. On the right you have a totally different universe, Set 2, because its Either combines types from Set 1.

(I am not an expert, so maybe I got the indices of the universes wrong, but they definitely are different universes)

Date: 2022-11-21 10:08 am (UTC)
From: [personal profile] sassa_nf
Well, but these are not necessarily correct logical constructs - because Haskell and Scala allow to populate ⊥. So you need some stronger type system.

∀A.X has the same problems as dependent types. Are you allowed to get a type that includes itself?

Something along these lines:

Suppose Y=∀A.X. doesn't it mean that X exists for Y as well, since Y is a type? Doesn't that mean X proves itself?

Date: 2022-11-21 03:28 pm (UTC)
From: [personal profile] sassa_nf
> I don't think there are problems due to Y "proving itself". But there are no nice set-theoretic models for this.

Well, the problem is that it allows to populate any type, once you add polymorphism. But you want to consider a subset that doesn't allow this.

The way this is solved is by adding a so-called tower of universes. Then you can include Y "in X" - but that X is on the next universe from Y.

Also, really there is no difference between how you "simplify" F(A)×G(A) and F(A)+G(A). Why? Because it actually holds for any type of next universe (_×_ and _+_ are just examples, but the same can be done to _→_) of any arity. Since there really is no equality, the correspondence is arbitrary.
Edited Date: 2022-11-21 03:29 pm (UTC)

Date: 2022-11-21 06:46 pm (UTC)
From: [personal profile] sassa_nf
What makes you think that what you want is a trivial type? :)

Your Scala example is not the function that we need. There is an implied argument, hidden as run: F[A] method.

Your Scala example does not (and I believe cannot) show equality, because the size of types is different. I.e. f . g /= id.

You don't need to automatically define the inverse. Just show that there exist such f and g that f . g == id, and that g . f == id. Then the types are equal (in the sense defined by HoTT; if that's not suitable, you need to define your own equality and everything that comes with it).
Edited Date: 2022-11-21 06:53 pm (UTC)

Date: 2022-11-21 03:38 pm (UTC)
From: [personal profile] sassa_nf
> Consider as an example a value of type ∀A. A × A → A, there are 2 distinct values of that type.

No, no, no. You are confusing the level where you are working with the type.

Try to do the same to F(A)+G(A), which gets translated into F+G. If you think about it as a value, then it has either F, or G, but not both.

The only level where (2) can even be discussed, is really type construction. That is, a type (A : Set) → F(A)+0G(A) vs type ((A : Set) → F(A))+1((B : Set) → G(B)) = F+1G. I am not sure how to even begin to express something like an equivalence - how to capture the existence of an isomorphism.
Edited Date: 2022-11-21 03:43 pm (UTC)

Date: 2022-11-21 04:24 pm (UTC)
From: [personal profile] sassa_nf
Suppose we use extensionality axiom to prove the equality. It requires an existence of two functions, a composition of which each way is the corresponding id.

Let's see what functions we are talking about.

f : (F G : (A : Set)) → (F + G) → (A : Set) → (F A) + (G A)

f F G (Left .F) = λ A → Left {! What goes here? !}
... --- ditto for Right G

-- similar for g working the other way.


I have a problem filling in the hole. It must have a way of constructing a value of an arbitrary type F(_), which includes the type λ_ → ⊥.

A problem with g is even worse. It should map any value of type λ A → F(A)+G(A). But there is just one such value (not proven either) for two values of type F+G.
Edited Date: 2022-11-21 04:33 pm (UTC)

Date: 2022-11-21 07:06 pm (UTC)
From: [personal profile] sassa_nf
No, what you presented is not the meaning of translation from ∀A.F(A)+G(A) to (∀A.F(A))+(∀A.G(A)).

First consider what a polymorphic term is. Let's say, write a function f :: ()->forall a.(a,()). In Haskell this amounts to a bottom. Because that's the only way to construct a term that can be any type.

In essence, a function doesn't know the context, but the return value will fit any context.
Edited Date: 2022-11-21 07:09 pm (UTC)

Date: 2022-11-21 11:39 pm (UTC)
From: [personal profile] sassa_nf
> Where is the error, in your opinion?

The error is in the wrong level where you make assertions. The error is in the rejection of the truth that ∀A.X in polymorphic lambda is nothing but a function (A : Type)→X. This last one is actually quite strange, since that's exactly the definition of a function in FP.


> I wrote the Scala code that maps from a value of type ∀A.F(A)+G(A) to a value of type (∀A.F(A))+(∀A.G(A)).

No, you wrote code that translates values of type F(A)+G(A) for a given A. The subtle difference becomes apparent once you accept that ∀A.X in polymorphic lambda is nothing but a function (A : Type)→X.

The value of type ∀A.F(A)+G(A) is not Either, but a function that gets you Either F(A) G(A), if you pass it a type.

Date: 2022-11-22 10:43 am (UTC)
From: [personal profile] sassa_nf
> what would be the correct Scala code.

Two problems with this :)

1. Scala is not exactly consistent - you are allowed to populate type 0

2. How can I write correct code for the thing that I claim should not be possible?


> ∀A. F(A)+∀A. G(A) vs ∀A. (F(A) + G(A))

Ok, let's drop for now the question of the direction. I may have gotten confused. My problem is in a different place anyway.

So, you do say ∀A.A=0. Let's start with your explanation of what makes ∀A. F(A) different _for_ _all_ F.

I insist on you writing a function ()->forall a.F(a), not just forall a.F(a), because we can agree that X and ()->X are isomorphic, but writing a function will expose what is wrong with the claim that you wrote a function transforming forall a.F(a), not a function for transforming F(a) for a given a. (To me it is so obvious - the body of a function is always for a given argument - in this case a type)

The next step is to transform ()->forall a.F(a) into a desired type, not forall a.F(a). Again, they are isomorphic, right?
Edited Date: 2022-11-22 10:49 am (UTC)

Date: 2022-11-22 02:08 pm (UTC)
From: [personal profile] sassa_nf
> As I already said, I'm working with a lambda calculus, or pure subset of Scala or Haskell that does not allow us to inhabit 0.

You saying so doesn't make it so :)

You've discarded the idea of the towers of universes, and allowed to include the type in itself. Isn't this how one might populate an empty type?


> .run

I've explained that in presence of invocation of _methods_ you are no longer dealing with the types you declared - you can no longer treat Scala types as direct translation of lambda. There is an implicit argument that we could call "a dictionary of functions". Suddenly it is no longer a proof of equality of types.

Profile

chaource: (Default)
chaource

February 2026

S M T W T F S
123 45 67
8 910 11121314
151617181920 21
22232425262728

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Feb. 23rd, 2026 01:40 am
Powered by Dreamwidth Studios