Algebraically Interpreting Polymorphism

So, I understand the basic algebraic interpretation of types:

Either ab ~ a + b (a, b) ~ a * b a -> b ~ b^a () ~ 1 Void ~ 0 -- from Data.Void 

... and that these relationships are true for specific types such as Bool , unlike polymorphic types of type a . I also know how to translate type signatures with polymorphic types into their concrete type representations by simply translating the Church encoding according to the following isomorphism:

 (forall r . (a -> r) -> r) ~ a 

So, if I have:

 id :: forall a . a -> a 

I know this does not mean id ~ a^a , but it actually means:

 id :: forall a . (() -> a) -> a id ~ () ~ 1 

Similarly:

 pair :: forall r . (a -> b -> r) -> r pair ~ ((a, b) -> r) - > r ~ (a, b) ~ a * b 

This brings me to my question. What is an "algebraic" interpretation of this rule:

 (forall r . (a -> r) -> r) ~ a 

For each specific type isomorphism, I can specify an equivalent algebraic rule, such as:

 (a, (b, c)) ~ ((a, b), c) a * (b * c) = (a * b) * c a -> (b -> c) ~ (a, b) -> c (c^b)^a = c^(b * a) 

But I do not understand algebraic equality, similar:

 (forall r . (a -> r) -> r) ~ a 
+53
types haskell
May 04 '12 at 17:35
source share
5 answers

This is the famous Yoneda lemma for the identification function.

Check out this post for a readable introduction and any category theory textbook for more.

In short, given f :: forall r. (a -> r) -> r f :: forall r. (a -> r) -> r , you can use f id to get a , and vice versa, given x :: a you can take ($x) to get forall r. (a -> r) -> r forall r. (a -> r) -> r .

These operations are mutually inverse. Evidence:

Obviously ($x) id == x . I will show that

($(f id)) == f ,

since functions are equal when they are equal for all arguments, take x :: a -> r and show that

($(f id)) x == fx ie

x (f id) == fx .

Since f is polymorphic, it works like a natural transform; this is the naturalness diagram for f :

  f_A Hom(A, A) → A (x.) ↓ ↓ x Hom(A, R) → R f_R 

So x . f == f . (x.) x . f == f . (x.) x . f == f . (x.) .

Connect identifier, (x . f) id == fx . QED

+28
May 05 '12 at
source share

(rewritten for clarity)

There seem to be two parts to your question. One of them is implied and asks what is an algebraic interpretation of forall , and the other asks about the cont / Yoneda transformation, which sdcvvc's answer already covers quite well.

I will try to refer to the algebraic interpretation of forall for you. You note that A -> B is B^A , but I would like to take this step further and expand it to B * B * B * ... * B ( |A| times). Although we do have exponentiation as a notation for repeated multiplication, that is, a more flexible notation (upper case Pi) representing arbitrary indexed products. Pi has two components: the range of values ​​that we want to multiply, and the expression that we multiply. For example, at the value level, you can express the factorial function as fact i = ∏ [1..i] (λx -> x) .

Returning to the world of types, we can consider the exponential operator in accordance with A -> B ~ B^A as Pi: B^A ~ ∏ A (λ_ -> B) . This suggests that we define an A -ric product B s such that B cannot depend on the chosen A Of course, this is equivalent to a simple exponentiation, but it allows you to move on to cases in which there is a dependency.

In the most general case, we get dependent types, for example, what you see in Agda or Coq: in Agda syntax replicate : Bool -> ((n : Nat) -> Vec Bool n) is one of the possible applications of type Pi, which can be expressed more explicitly as replicate : Bool -> ∏ Nat (Vec Bool) , or further as replicate : ∏ Bool (λ_ -> ∏ Nat (Vec Bool)) .

Note that, as you might expect from the underlying algebra, you can fuse both in the replicate definition above into one range in the Cartesian product of the domains: ∏ Bool (\_ -> ∏ Nat (Vec Bool)) is equivalent ∏ (Bool, Nat) (λ(_, n) -> Vec Bool n) exactly the same as at the value level. This is just messy in terms of type theory.

I understand that your question was about polymorphism, so I will focus on dependent types, but they matter: forall in Haskell is roughly equivalent to with a domain above the type (type) of types, * . Indeed, the functional-like behavior of polymorphism can be observed directly in the core of the GHC, which calls them capital lambdas (Λ). Thus, the polymorphic type is forall a. a -> a forall a. a -> a is actually just ∏ * (Λ a -> (a -> a)) (using the notation Λ now to distinguish between types and values), which can be expanded to an infinite product (Bool -> Bool, Int -> Int, () -> (), (Int -> Bool) -> (Int -> Bool), ...) for each possible type. Activating a type variable simply projects the appropriate element from the * -ary product (or by applying a type function).

Now, for the big part that I missed in my original version of this answer: parametricity. Parametricity can be described in several different ways, but none of those that I know (view types as relationships or (di) naturalness in category theory) really have a very algebraic interpretation. However, for our purposes, this boils down to something rather simple: you cannot map the image to * . I know that GHC allows you to do this at the type level with family types, but you can only cover the final fragment * , so there are always points at which your type family is undefined.

What this means, from the point of view of polymorphism, is that any function of type F that we write in ∏ * F must either be constant (i.e. completely ignore the type that was polymorphic), or pass the type through without changes. Thus, ∏ * (Λ _ -> B) true, since it ignores its argument and corresponds to forall a. B forall a. B Another case is something like ∏ * (Λ x -> Maybe x) , which corresponds to forall a. Maybe a forall a. Maybe a , which does not ignore the type argument, but only "passes it". Thus, a ∏ A , which has an irrelevant region A (for example, when A = * ), can be considered as a larger number of indexed intersections A (taking into account common elements in all instances of the index) and not a product.

Most importantly, at the value level, parametricity rules prevent any ridiculous behavior that might suggest that the types are larger than they really are. Since we do not have a typecase, we cannot construct a value of type forall a. B forall a. B , which does something different depending on what instance of A was created. Thus, although the type is technically a function * -> B , it is always a constant function and thus equivalent to a single value of B Using the interpretation of , it is really equivalent to the infinite * -arrier product B s, but those values ​​of B must always be the same, so the infinite product effectively exceeds the unit B

Similarly, although ∏ * (Λ x -> (x -> x)) (aka, forall a. a -> a ) is technically equivalent to an infinite product of functions, none of these functions can check the type, therefore all are limited only to returning them input value, not a funny business, for example (+1) : Int -> Int when creating an instance of Int . Since there is only one function (subject to the full language) that cannot check the type of its argument, but must return a value of the same type, an infinite product is thus equal to a single value.

Now about your direct question about (forall r . (a -> r) -> r) ~ a . First, to express the ~ operator more formally. This is really an isomorphism, so we need two functions going back and forth, and the argument that they are inverted.

 data Iso ab = Iso { to :: a -> b , from :: b -> a -- proof1 :: forall x. to (from x) == x -- proof2 :: forall x. from (to x) == x } 

and now we express your initial question in more formal terms. Your question boils down to constructing the following term (unproductive, so the GHC has problems with it, but we will survive):

 forall a. Iso (forall r. (a -> r) -> r) a 

Which, using my earlier terminology, is ∏ * (Λ a -> Iso (∏ * (Λ r -> ((a -> r) -> r))) a) . Once again, we have an endless product that cannot verify its type argument. Through manual work, we can argue that only possible values ​​that take into account the parametricity rules (two other proofs are automatically observed) for to and from are ($ id) and flip id .

If this seems unsatisfactory, it is probably because the algebraic interpretation of forall really added nothing to the proof. This is a really simple theory of the old type, but I hope that I can provide something that seems a little less categorical than the Yoneda form. It is worth noting that in fact we do not need to use parametricity to write proof1 and proof2 . Parametricity only enters the image when we want to state that ($ id) and flip id are our only options for to and from (which we cannot prove in Agda or Coq for this reason).

+17
May 04 '12 at 22:35
source share

To (try) to answer the actual question (which is less interesting than the answers to the broader questions raised), the question is poorly formed due to a "type error"

 Either ~ (+) (,) ~ (*) (->) b ~ flip (^) () ~ 1 Void ~ 0 

All these types of maps are integers and constructors for functions in natural languages ​​build. In a sense, you have a functor from the type category to the natural category. In the other direction, you “forget” things, because types preserve the algebraic structure, while straight people discard it. That is, given Either () () , you can get a unique natural, but given naturalness, you can get many types.

But this is different:

 (forall r . (a -> r) -> r) ~ a 

It maps a type to another type! It is not part of the above functor. This is just an isomorphism in the type category. So let's specify another character, <=>

Now we have

 (forall r . (a -> r) -> r) <=> a 

Now you will notice that we can not only send types to nats and arrows to arrows, but also some isomorphisms to other isomorphisms:

 (a, (b, c)) <=> ((a, b), c) ~ a * (b * c) = (a * b) * c 

But something subtle is happening here. In a sense, the last isomorphism on pairs is true, since the algebraic identity is true. This means that the “isomorphism” in the latter simply means that these two types are equivalent in the image of our functor for nats.

We need to prove the first isomorphism directly, namely, where we begin to get to the main question - our functor for nat is given, which means forall r. ? But the answer is forall r. is neither a type nor a significant arrow between types.

By introducing forall, we moved away from first-order types. There is no reason to expect that everyone should fit into our aforementioned Functor, and indeed, this is not so.

So, we can investigate, like the others above, why isomorphism takes place (which is very interesting), but at the same time we abandoned the algebraic core of the question. To the question that can be answered, I think, there is a category of higher orders and constructors as arrows between them, what is the meaning of Functor to?

Edit : So now I have a different approach that shows why adding polymorphism makes things go crazy. Let's start with a simpler question - does a given polymorphic type have zero or more than zero? This is a problem such as a living room problem , and ends up with a problem in Carrie-Howard being changed realizability , since it is the same as asking if any formula is implemented in some logic in the corresponding computational model. Now, as this page explains, this is solvable in a simply typed lambda calculus, but PSPACE-complete. But as soon as we move on to something more complex, adding, for example, polymorphism and moving to the system F, then it goes on to insoluble ones!

So, if we cannot decide where an arbitrary type is populated, then we obviously cannot decide how many inhabitants it has!

+7
May 7 '12 at 10:38 PM
source share

This is an interesting question. I do not have a complete answer, but it is too long for a comment.

A signature of the type (forall r. (a -> r) -> r) can be expressed as I, saying

For any type of r you want to name, if you give me a function that takes a and creates r , I will return r you.

Now this should work for any type r , but may be a specific type a . So the way for me in this neat trick is to sit somewhere somewhere a , which I pass to the function (which creates r for me) and then pass you r .

But if I have a sitting, I can give it to you:

If you give me 1, I will give you a .

which corresponds to a signature of type 1 -> a or just a . According to this unofficial argument, we have

(forall r. (a -> r) -> r) ~ a

The next step would be to generate the corresponding algebraic expression, but I do not understand how algebraic quantities interact with universal quantification. We may need to wait for an expert!

+4
May 04 '12 at 18:27
source share

A few links to nLab :




Thus, in the settings of category theory:

 Type | Modeled¹ as | In category -------------------+---------------------------+------------- Unit | Terminal object | CCC Bottom | Initial object | Record | Product | Union | Sum (coproduct) | Function | Exponential | -------------------+---------------------------+------------- Dependent product² | Right adjoint to pullback | LCCC Dependent sum | Left adjoint to pullback | 

¹) in the corresponding category ─ CCC for the general and non-polymorphic subset of Haskell ( link ), CPO for undefined features of Haskell ( link ), LCCC for dependent languages.

²) forall quantification is a particular case of a dependent product:

 ∀(x :: *). y[x] ~ ∏(x : Set)y[x] 

where Set is the universe of all small types.

+4
May 6 '12 at 14:29
source share



All Articles