My solutions to Bartosz Milewski "Category Theory for Programmers" challenges.
1. Implement, as best as you can, the identity function in your favorite language (or the second favorite, if your favorite language happens to be Haskell).
# the identity arrow is implemented as the identity function that just returns back its argument.
def identity(x):
return x
2. Implement the composition function in your favorite language. It takes two functions as arguments and returns a function that is their composition.
def compose (f, g):
return lambda x: f(g(x))
(see full example at 1.4.2.py)
h :: (b -> c) -> (a -> b) -> a -> c
h f g = f . g
Function comparison for partial functions is undefined for Turing Machin in general case, because of the Halting Problem. Functions might be compared:
- lexically (possibly via reduction)
- by comparing samples of function return values for a number of samples, or for all samples in case of total functions.
- in case of composition with identity, we could cheat by defining the following (in haskell):
id . f == f . id
If links are composable, then they are morphisms, in which case WWW is a category.
Friendships do not compose in general, hence they are not morphisms.
When a directed graph's edges are associative, compose and every node has an identity morphism, then such directed graph is a category.
1. Define a higher-order function (or a function object) memoize in your favorite language. This function takes a pure function f as an argument and returns a function that behaves almost exactly like f , except that it only calls the original function once for every argument, stores the result internally, and subsequently returns this stored result every time it’s called with the same argument. You can tell the memoized function from the original by watching its performance. For instance, try to memoize a function that takes a long time to evaluate. You’ll have to wait for the result the first time you call it, but on subsequent calls, with the same argument, you should get the result immediately.
import math
def memoize(f):
memo = {}
def memoized(x):
if x not in memo:
memo[x] = f(x)
return memo[x]
return memoized
# Use example:
# $ python
# > import math
# > import _271
# > fact = _271.memoize(math.factorial)
# > fact(1000000)
2. Try to memoize a function from your standard library that you normally use to produce random numbers. Does it work?
No, it does not work.
3. Most random number generators can be initialized with a seed. Implement a function that takes a seed, calls the random number generator with that seed, and returns the result. Memoize that function. Does it work?
It works, provided the random number generator seed value can be fixed, e.g.:
import random
# define a random number generator with seed:
def rand(seed):
fseed = math.factorial(1000000) # make it slow to compute i.o.t. test memorization
random.seed(fseed)
return random.random()
# > mrand = _271.memoize(_271.rand)
# > mrand(10)
# 0.27256627902952435
There are 4 basic functions Bool -> Bool
:
not :: Bool -> Bool
and :: Bool -> Bool
or :: Bool -> Bool
xor :: Bool -> Bool
and infinitely many compositions of these 4 basic functions. I don't think that I can implement them all.
6. Draw a picture of a category whose only objects are the types Void , () (unit), and Bool ; with arrows corresponding to all possible functions between these types. Label the arrows with the names of the functions.
dot -Tpng _myFile.dot > _myFile.png
digraph G {
Void -> "()" [label = "absurd"]
Void -> Bool [label = "absurd"]
Void -> Void [label = "id"]
"()" -> "()" [label = "id"]
"()" -> Bool:nw [label = "true"]
"()" -> Bool [label = "false"]
Bool -> Bool [label = "id"]
Bool:sw -> Bool:nw [label = "not"]
Bool:sw -> Bool:nw [label = "and"]
Bool:sw -> Bool:nw [label = "or"]
Bool:sw -> Bool:nw [label = "xor"]
Bool -> "()" [label = "unit"]
}
digraph G
{
"()" -> "()" [label = "id"]
}
digraph G
{
"()" -> "()" [label = "id"]
"()":w -> "()":w [label = "id.id"]
}
digraph G
{
A -> A [label = "id"]
A -> B [label = "f"]
B -> B [label = "id"]
}
digraph G
{
"" -> "" [label = "id"]
"":w -> "":w [label = "a"]
"":w -> "":w [label = "b"]
"":w -> "":w [label = "c"]
"":w -> "":w [label = "d"]
"":w -> "":w [label = "e"]
"":w -> "":w [label = "f"]
"":w -> "":w [label = "g"]
"":w -> "":w [label = "h"]
"":w -> "":w [label = "..."]
"":w -> "":w [label = "z"]
}
(a) A set of sets with the inclusion relation: 𝐴 is included in 𝐵 if every element of 𝐴 is also an element of 𝐵.
A ⊆ B, if ∀x ∈ A : x ∈ B.
if ∀y ∈ B : y ∈ A ∴ B ⊆ A
∴ (A ⊆ B)^(B ⊆ A) ∴ A = B
Hence, it's a Partial Order.
(b) C++ types with the following subtyping relation: T1 is a subtype of T2 if a pointer to T1 can be passed to a function that expects a pointer to T2 without triggering a compilation error.
I don't care about C++, but it looks like it's a Partial Order too.
3. Considering that Bool is a set of two values True and False , show that it forms two (set-theoretical) monoids with respect to, respectively, operator && (AND) and || (OR).
data Bool = True | False
a) Consider the case of && (AND):
By the definition of the truth table for AND:
True && True => True
True && False => False
False && True => False
False && False => False,
Since
. == && (a binary operator)
∴
True . True => True
True . False => False
False . True => False
False . False => False,
Since
T == True
F == False
a :: Bool
∴
T . T => a
T . a => a
a . T => a
F . F => F,
Let T == id,
∴
a . a => a
id . a => a
a . id => a
a . a => a
∴
a . id => a
id . a => a
a . a => a
∴
a . id == id . a => a
where id == True
Since && acts as a binary operator . and True acts as a unit (id), therefore, by definition, Bool forms a monoid in respect to && (AND).
b) W.r.t || (OR): similar to a):
By the definition of the truth table for OR:
True || True => True
True || False => True
False || True => True
False || False => False,
Since
. == || (a binary operator)
and
Since
T == True
F == False
a :: Bool
∴
T . T => T
a . F => a
F . a => a
F . F => F
∴
a . a => a
a . F => a
F . a => a
a . a => a
∴
a . F => a
F . a => a
a . a => a,
Let F == id,
∴
a . id => a
id . a => a
a . a => a
∴
a . id == id . a => a
where id == False
Since || acts as a binary operator . and False acts as a unit (id), therefore, by definition, Bool forms a monoid in respect to || (OR).
4. Represent the Bool monoid with the AND operator as a category: List the morphisms and their rules of composition.
digraph G
{
Bool -> Bool [label = "id (&& True)"]
Bool:w -> Bool:w [label = "(&& False)"]
}
digraph G
{
N -> N [label = "id (+ 0 % 3)"]
N:w -> N:w [label = "(+1 % 3)"]
N:s -> N:s [label = "(+2 % 3)"]
}