Difference between revisions of "User:Michiexile/MATH198/Lecture 4"

From HaskellWiki
Jump to navigation Jump to search
Line 8: Line 8:
   
 
This, too, is how we construct vector spaces: recall that <math>\mathbb R^n</math> is built out of tuples of elements from <math>\mathbb R</math>, with pointwise operations. This constructions reoccurs all over the place - sets with structure almost always have the structure carry over to products by pointwise operations.
 
This, too, is how we construct vector spaces: recall that <math>\mathbb R^n</math> is built out of tuples of elements from <math>\mathbb R</math>, with pointwise operations. This constructions reoccurs all over the place - sets with structure almost always have the structure carry over to products by pointwise operations.
  +
  +
The product of sets is determined by the projection maps <math>p_1:(s,t)\mapsto s</math> and <math>p_2: (s,t)\mapsto t</math>. You know any element of <math>S\times T</math> by knowing what its two coordinates are, and any element of <math>S</math> with an element of <math>T</math> determines exactly one element of <math>S\times T</math>.
   
 
Given the cartesian product in sets, the important thing about the product is that we can extract both parts, and doing so preserves any structure present, since the structure is defined pointwise.
 
Given the cartesian product in sets, the important thing about the product is that we can extract both parts, and doing so preserves any structure present, since the structure is defined pointwise.
Line 28: Line 30:
   
 
This is, in fact, the product construction applied to <math>Cat</math> - or even to <math>CAT</math>: we get functors <math>P_1,P_2</math> picking out the first and second components, and everything works out exactly as in the cases above.
 
This is, in fact, the product construction applied to <math>Cat</math> - or even to <math>CAT</math>: we get functors <math>P_1,P_2</math> picking out the first and second components, and everything works out exactly as in the cases above.
  +
  +
We keep writing ''the product'' here. The justification for this is:
  +
'''Theorem''' If <math>P</math> and <math>P'</math> are both product objects for the pair <math>(A,B)</math>, then they are isomorphic.
  +
  +
'''Proof''' Consider the diagram:
  +
(( Diagram ))
  +
Both the vertical maps have unique existence, by the defining property of the product. Hence the composition of these two maps, is an endo-map of <math>P</math> (<math>P'</math>) such that both projections factor through this endo-map. However, the identity map <math>1_P</math> (<math>1_{P'}</math>) is also such an endo-map, and again, by the definition of the product, a map to <math>P</math> (</math>P'</math>) that the projections factor through is uniquely determined.
  +
Hence the composition is the identity, and this argument holds, mutatis mutandis, for the other inverse. Hence these vertical maps are isomorphisms, inverse to each other, and thus <math>P, P'</math> are isomorphic. QED.
  +
   
 
===Coproduct===
 
===Coproduct===
Line 52: Line 63:
 
In the category of sets, the coproduct construction is one where we can embed both sets into the coproduct, faithfully, and the result has no additional structure beyond that. Thus, the coproduct in set, is the disjoint union of the included sets: both sets are included without identifications made, and no extra elements are introduced.
 
In the category of sets, the coproduct construction is one where we can embed both sets into the coproduct, faithfully, and the result has no additional structure beyond that. Thus, the coproduct in set, is the disjoint union of the included sets: both sets are included without identifications made, and no extra elements are introduced.
   
  +
'''Proposition''' If <math>C,C'</math> are both coproducts for some <math>A,B</math>, then they are isomorphic.
   
  +
The proof is almost exactly the same as the proof for the product case.
   
 
* Diagram definition
 
* Diagram definition
Line 61: Line 74:
 
===Algebra of datatypes===
 
===Algebra of datatypes===
   
Recall from last week that we can consider endofunctors as container datatypes. A few nice endofunctors we may have around include (with some abuse of notation):
+
Recall from [User:Michiexile/MATH198/Lecture_3|Lecture 3] that we can consider endofunctors as container datatypes.
  +
Some of the more obvious such container datatypes include:
 
<haskell>
 
<haskell>
data 0 a = Boring
+
data 1 a = Empty
data 1 a = Singleton a
+
data T a = T a
 
</haskell>
 
</haskell>
  +
These being the data type that has only one single element and the data type that has exactly one value contained.
with the obvious Functor implementations. From these, we can start building new container types, such as:
 
  +
  +
Using these, we can generate a whole slew of further datatypes. First off, we can generate a data type with any finite number of elements by <math>n = 1 + 1 + \dots + 1</math> (<math>n</math> times). Remember that the coproduct construction for data types allows us to know which summand of the coproduct a given part is in, so the single elements in all the <hask>1</hask>s in the definition of <hask>n</hask> here are all distinguishable, thus giving the final type the required number of elements.
  +
Of note among these is the data type <hask>Bool = 2</hask> - the Boolean data type, characterized by having exactly two elements.
  +
  +
Furthermore, we can note that <math>1\times T = T</math>, with the isomorphism given by the maps
 
<haskell>
 
<haskell>
  +
f (Empty, T x) = T x
Bool = 0 + 0
 
Maybe = 1 + 0
+
g (T x) = (Empty, T x)
Pair = 1 * 1
 
 
</haskell>
 
</haskell>
  +
and we can use this to produce recursive definitions
 
  +
Thus we have the capacity to ''add'' and ''multiply'' types with each other. We can verify, for any types <math>A,B,C</math>
  +
<math>A\times(B+C) = A\times B + A\times C</math>
  +
  +
We can thus make sense of types like <math>T^3+2T^2</math> (either a triple of single values, or one out of two tagged pairs of single values).
  +
  +
This allows us to start working out a calculus of data types with versatile expression power. We can produce recursive data type definitions by using equations to define data types, that then allow a direct translation back into Haskell data type definitions, such as:
  +
<math>List = 1 + T\times List</math>
  +
<math>BinaryTree = T\times (1+BinaryTree\times BinaryTree)</math>
  +
<math>TernaryTree = T\times (1+TernaryTree\times TernaryTree\times TernaryTree)</math>
  +
<math>GenericTree = T\times (1+List\circ GenericTree)</math>
  +
  +
The real power of this way of rewriting types comes in the recognition that we can use algebraic methods to reason about our data types. For instance:
  +
 
<haskell>
 
<haskell>
List = 0 + 1*List
+
List = 1 + T * List
 
= 1 + T * (1 + T * List)
NonEmptyList = 1 + NonEmptyList
 
 
= 1 + T * 1 + T * T* List
 
= 1 + T + T * T * List
 
</haskell>
 
</haskell>
  +
and to argue in a highly algebraic manner about data type definitions
 
  +
so a list is either empty, contains one element, or contains at least two elements. Using, though, ideas from the theory of power series, or from continued fractions, we can start analyzing the data types using steps on the way that seem completely bizarre, but arriving at important property. Again, an easy example for illustration:
  +
 
<haskell>
 
<haskell>
List = 0 + 1*List
+
List = 1 + T * List -- and thus
  +
List - T * List = 1 -- even though (-) doesn't make sense for data types
= 0 + 1*(0 + 1*List)
 
  +
(1 - T) * List = 1 -- still ignoring that (-)...
= 0 + 1*0 + 1*1*List
 
  +
List = 1 / (1 - T) -- even though (/) doesn't make sense for data types
= 0 + 1 + 1*1*List
 
  +
= 1 + T + T*T + T*T*T + ... -- by the geometric series identity
 
</haskell>
 
</haskell>
  +
and hence, we can conclude - using formally algebraic steps in between - that a list by the given definition consists of either an empty list, a single value, a pair of values, three values, et.c.
  +
  +
At this point, I'd recommend anyone interested in more perspectives on this approach to data types, and thinks one may do with them, to read the following references:
  +
  +
====Blog posts====
  +
  +
====Research papers====
  +
  +
d for data types
  +
7 trees into 1
  +
  +
===Homework===
  +
  +
# What are the products in the category <math>C(P)</math> of a poset <math>P</math>? What are the coproducts?
  +
# Prove that any two coproducts are isomorphic.
  +
# Write down the type declaration for at least two of the example data types from the section of the algebra of datatypes, and write a <hask>Functor</hask> implementation for each.

Revision as of 19:19, 12 October 2009

IMPORTANT NOTE: THESE NOTES ARE STILL UNDER DEVELOPMENT. PLEASE WAIT UNTIL AFTER THE LECTURE WITH HANDING ANYTHING IN, OR TREATING THE NOTES AS READY TO READ.

Product

Recall the construction of a cartesian product: for sets , the set .

The cartesian product is one of the canonical ways to combine sets with each other. This is how we build binary operations, and higher ones - as well as how we formally define functions, partial functions and relations in the first place.

This, too, is how we construct vector spaces: recall that is built out of tuples of elements from , with pointwise operations. This constructions reoccurs all over the place - sets with structure almost always have the structure carry over to products by pointwise operations.

The product of sets is determined by the projection maps and . You know any element of by knowing what its two coordinates are, and any element of with an element of determines exactly one element of .

Given the cartesian product in sets, the important thing about the product is that we can extract both parts, and doing so preserves any structure present, since the structure is defined pointwise.

This is what we use to define what we want to mean by products in a categorical setting.

Definition Let be a category. The product of two objects is an object equipped with maps and such that any other object with maps has a unique map such that both maps from factor through the .

In the category of Set, the unique map from to would be given by .

The uniqueness requirement is what, in the theoretical setting, forces the product to be what we expect it to be - pairing of elements with no additional changes, preserving as much of the structure as we possibly can make it preserve.

In the Haskell category, the product is simply the Pair type:

Product a b = (a,b)

and the projection maps are just fst, snd.

Recall from the first lecture, the product construction on categories: objects are pairs of objects, morphisms are pairs of morphisms, identity morphisms are pairs of identity morphisms, and composition is componentwise.

This is, in fact, the product construction applied to - or even to : we get functors picking out the first and second components, and everything works out exactly as in the cases above.

We keep writing the product here. The justification for this is: Theorem If and are both product objects for the pair , then they are isomorphic.

Proof Consider the diagram: (( Diagram )) Both the vertical maps have unique existence, by the defining property of the product. Hence the composition of these two maps, is an endo-map of () such that both projections factor through this endo-map. However, the identity map () is also such an endo-map, and again, by the definition of the product, a map to (</math>P'</math>) that the projections factor through is uniquely determined. Hence the composition is the identity, and this argument holds, mutatis mutandis, for the other inverse. Hence these vertical maps are isomorphisms, inverse to each other, and thus are isomorphic. QED.


Coproduct

The other thing you can do in a Haskell data type declaration looks like this:

Coproduct a b = A a | B b

and the corresponding library type is Either a b = Left a | Right b.

This type provides us with functions

A :: a -> Coproduct a b
B :: b -> Coproduct a b

and hence looks quite like a dual to the product construction, in that the guaranteed functions the type brings are in the reverse directions from the arrows that the product projection arrows.

So, maybe what we want to do is to simply dualize the entire definition?

Definition Let be a category. The coproduct of two objects is an object equipped with maps and such that any other object with maps has a unique map such that and .

In the Haskell case, the maps are the type constructors . And indeed, this Coproduct, the union type construction, is the type which guarantees inclusion of source types, but with minimal additional assumptions on the type.

In the category of sets, the coproduct construction is one where we can embed both sets into the coproduct, faithfully, and the result has no additional structure beyond that. Thus, the coproduct in set, is the disjoint union of the included sets: both sets are included without identifications made, and no extra elements are introduced.

Proposition If are both coproducts for some , then they are isomorphic.

The proof is almost exactly the same as the proof for the product case.

  • Diagram definition
  • Disjoint union in Set
  • Coproduct of categories construction
  • Union types

Algebra of datatypes

Recall from [User:Michiexile/MATH198/Lecture_3|Lecture 3] that we can consider endofunctors as container datatypes. Some of the more obvious such container datatypes include:

data 1 a = Empty
data T a = T a

These being the data type that has only one single element and the data type that has exactly one value contained.

Using these, we can generate a whole slew of further datatypes. First off, we can generate a data type with any finite number of elements by ( times). Remember that the coproduct construction for data types allows us to know which summand of the coproduct a given part is in, so the single elements in all the 1s in the definition of n here are all distinguishable, thus giving the final type the required number of elements. Of note among these is the data type Bool = 2 - the Boolean data type, characterized by having exactly two elements.

Furthermore, we can note that , with the isomorphism given by the maps

f (Empty, T x) = T x
g (T x) = (Empty, T x)

Thus we have the capacity to add and multiply types with each other. We can verify, for any types

We can thus make sense of types like (either a triple of single values, or one out of two tagged pairs of single values).

This allows us to start working out a calculus of data types with versatile expression power. We can produce recursive data type definitions by using equations to define data types, that then allow a direct translation back into Haskell data type definitions, such as:

The real power of this way of rewriting types comes in the recognition that we can use algebraic methods to reason about our data types. For instance:

List = 1 + T * List 
     = 1 + T * (1 + T * List) 
     = 1 + T * 1 + T * T* List 
     = 1 + T + T * T * List

so a list is either empty, contains one element, or contains at least two elements. Using, though, ideas from the theory of power series, or from continued fractions, we can start analyzing the data types using steps on the way that seem completely bizarre, but arriving at important property. Again, an easy example for illustration:

List = 1 + T * List    -- and thus
List - T * List = 1    -- even though (-) doesn't make sense for data types
(1 - T) * List = 1     -- still ignoring that (-)...
List = 1 / (1 - T)     -- even though (/) doesn't make sense for data types
     = 1 + T + T*T + T*T*T + ...  -- by the geometric series identity

and hence, we can conclude - using formally algebraic steps in between - that a list by the given definition consists of either an empty list, a single value, a pair of values, three values, et.c.

At this point, I'd recommend anyone interested in more perspectives on this approach to data types, and thinks one may do with them, to read the following references:

Blog posts

Research papers

d for data types 7 trees into 1

Homework

  1. What are the products in the category of a poset ? What are the coproducts?
  2. Prove that any two coproducts are isomorphic.
  3. Write down the type declaration for at least two of the example data types from the section of the algebra of datatypes, and write a Functor implementation for each.