Spaces:
Running
Running
Commit
·
c6fe084
1
Parent(s):
8b10f04
Remove duplicate implementations in markdown
Browse files- functional_programming/05_functors.py +132 -162
functional_programming/05_functors.py
CHANGED
@@ -103,7 +103,17 @@ def _(md):
|
|
103 |
return
|
104 |
|
105 |
|
106 |
-
@app.cell
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
def _(Callable, Functor, Generic, a, b, dataclass):
|
108 |
@dataclass
|
109 |
class Wrapper(Functor, Generic[a]):
|
@@ -152,28 +162,11 @@ def _(md):
|
|
152 |
## The List Wrapper
|
153 |
|
154 |
We can define a `ListWrapper` class to represent a wrapped list that supports `fmap`:
|
155 |
-
|
156 |
-
```python
|
157 |
-
@dataclass
|
158 |
-
class ListWrapper(Generic[a]):
|
159 |
-
value: list[a]
|
160 |
-
|
161 |
-
def fmap(self, func: Callable[[a], b]) -> "ListWrapper[b]":
|
162 |
-
return ListWrapper([func(x) for x in self.value])
|
163 |
-
|
164 |
-
|
165 |
-
>>> list_wrapper = ListWrapper([1, 2, 3, 4])
|
166 |
-
>>> list_wrapper.fmap(lambda x: x + 1)
|
167 |
-
ListWrapper(value=[2, 3, 4, 5])
|
168 |
-
>>> list_wrapper.fmap(lambda x: [x])
|
169 |
-
ListWrapper(value=[[1], [2], [3], [4]])
|
170 |
-
```
|
171 |
-
> Try using `ListWrapper` in the cell below.
|
172 |
""")
|
173 |
return
|
174 |
|
175 |
|
176 |
-
@app.cell
|
177 |
def _(Callable, Functor, Generic, a, b, dataclass):
|
178 |
@dataclass
|
179 |
class ListWrapper(Functor, Generic[a]):
|
@@ -190,6 +183,14 @@ def _(Callable, Functor, Generic, a, b, dataclass):
|
|
190 |
return ListWrapper, list_wrapper
|
191 |
|
192 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
193 |
@app.cell(hide_code=True)
|
194 |
def _(md):
|
195 |
md("""
|
@@ -314,17 +315,6 @@ def _(md):
|
|
314 |
return
|
315 |
|
316 |
|
317 |
-
@app.cell(hide_code=True)
|
318 |
-
def _(RoseTree, mo):
|
319 |
-
ftree = RoseTree(1, [RoseTree(2, []), RoseTree(3, [RoseTree(4, [])])])
|
320 |
-
|
321 |
-
with mo.redirect_stdout():
|
322 |
-
print(ftree)
|
323 |
-
print(ftree.fmap(lambda x: [x]))
|
324 |
-
print(ftree.fmap(lambda x: RoseTree(x, [])))
|
325 |
-
return (ftree,)
|
326 |
-
|
327 |
-
|
328 |
@app.cell(hide_code=True)
|
329 |
def _(Callable, Functor, Generic, a, b, dataclass, md):
|
330 |
@dataclass
|
@@ -372,6 +362,17 @@ def _(Callable, Functor, Generic, a, b, dataclass, md):
|
|
372 |
return (RoseTree,)
|
373 |
|
374 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
375 |
@app.cell(hide_code=True)
|
376 |
def _(md):
|
377 |
md("""
|
@@ -511,28 +512,12 @@ def _(ftree, list_wrapper, mo, wrapper):
|
|
511 |
@app.cell(hide_code=True)
|
512 |
def _(md):
|
513 |
md("""
|
514 |
-
And here is an `EvilFunctor`
|
515 |
-
|
516 |
-
```Python
|
517 |
-
@dataclass
|
518 |
-
class EvilFunctor(Functor, Generic[a]):
|
519 |
-
value: list[a]
|
520 |
-
|
521 |
-
def fmap(self, func: Callable[[a], b]) -> "EvilFunctor[b]":
|
522 |
-
return (
|
523 |
-
EvilFunctor([self.value[0]] * 2 + list(map(func, self.value[1:])))
|
524 |
-
if self.value
|
525 |
-
else []
|
526 |
-
)
|
527 |
-
|
528 |
-
def __repr__(self):
|
529 |
-
return repr(self.value)
|
530 |
-
```
|
531 |
""")
|
532 |
return
|
533 |
|
534 |
|
535 |
-
@app.cell
|
536 |
def _(Callable, Functor, Generic, a, b, dataclass):
|
537 |
@dataclass
|
538 |
class EvilFunctor(Functor, Generic[a]):
|
@@ -646,7 +631,7 @@ def _(ABC, Callable, Generic, a, abstractmethod, b, dataclass, id, md):
|
|
646 |
@app.cell(hide_code=True)
|
647 |
def _(md):
|
648 |
md("""
|
649 |
-
|
650 |
""")
|
651 |
return
|
652 |
|
@@ -675,8 +660,12 @@ def _(md):
|
|
675 |
One example is the **`Maybe`** type from Haskell, which is used to represent computations that can either result in a value (`Just a`) or no value (`Nothing`).
|
676 |
|
677 |
We can define the `Maybe` functor as below:
|
|
|
|
|
678 |
|
679 |
-
|
|
|
|
|
680 |
@dataclass
|
681 |
class Just(Generic[a]):
|
682 |
value: a
|
@@ -688,18 +677,25 @@ def _(md):
|
|
688 |
def __repr__(self):
|
689 |
return f"Just {self.value}"
|
690 |
|
|
|
691 |
@dataclass
|
692 |
class Maybe(Functor, Generic[a]):
|
693 |
value: None | Just[a]
|
694 |
|
695 |
def fmap(self, func: Callable[[a], b]) -> "Maybe[b]":
|
696 |
# Apply the function to the value inside `Just`, or return `Nothing` if value is None
|
697 |
-
return
|
|
|
|
|
698 |
|
699 |
def __repr__(self):
|
700 |
return repr(self.value) if self.value else "Nothing"
|
701 |
-
|
702 |
|
|
|
|
|
|
|
|
|
703 |
- **`Just`** is a wrapper that holds a value. We use it to represent the presence of a value.
|
704 |
- **`Maybe`** is a functor that can either hold a `Just` value or be `Nothing` (equivalent to `None` in Python). The `fmap` method applies a function to the value inside the `Just` wrapper, if it exists. If the value is `None` (representing `Nothing`), `fmap` simply returns `Nothing`.
|
705 |
|
@@ -710,33 +706,6 @@ def _(md):
|
|
710 |
return
|
711 |
|
712 |
|
713 |
-
@app.cell(hide_code=True)
|
714 |
-
def _(Callable, Functor, Generic, a, b, dataclass):
|
715 |
-
@dataclass
|
716 |
-
class Just(Generic[a]):
|
717 |
-
value: a
|
718 |
-
|
719 |
-
def __init__(self, value: a):
|
720 |
-
self.value = value.value if isinstance(value, Just) else value
|
721 |
-
|
722 |
-
def __repr__(self):
|
723 |
-
return f"Just {self.value}"
|
724 |
-
|
725 |
-
|
726 |
-
@dataclass
|
727 |
-
class Maybe(Functor, Generic[a]):
|
728 |
-
value: None | Just[a]
|
729 |
-
|
730 |
-
def fmap(self, func: Callable[[a], b]) -> "Maybe[b]":
|
731 |
-
return (
|
732 |
-
Maybe(Just(func(self.value.value))) if self.value else Maybe(None)
|
733 |
-
)
|
734 |
-
|
735 |
-
def __repr__(self):
|
736 |
-
return repr(self.value) if self.value else "Nothing"
|
737 |
-
return Just, Maybe
|
738 |
-
|
739 |
-
|
740 |
@app.cell(hide_code=True)
|
741 |
def _(Just, Maybe, ftree, inc, mo):
|
742 |
with mo.redirect_stdout():
|
@@ -792,7 +761,7 @@ def _(md):
|
|
792 |
There are three laws that categories need to follow.
|
793 |
|
794 |
1. The composition of morphisms needs to be **associative**. Symbolically, $f ∘ (g ∘ h) = (f ∘ g) ∘ h$
|
795 |
-
|
796 |
- Morphisms are applied right to left, so with $f ∘ g$ first $g$ is applied, then $f$.
|
797 |
|
798 |
2. The category needs to be **closed** under the composition operation. So if $f : B → C$ and $g : A → B$, then there must be some morphism $h : A → C$ in the category such that $h = f ∘ g$.
|
@@ -1066,31 +1035,49 @@ def _(md):
|
|
1066 |
### Category of List Concatenation
|
1067 |
|
1068 |
First, let’s define the category of list concatenation:
|
|
|
|
|
1069 |
|
1070 |
-
|
|
|
|
|
1071 |
@dataclass
|
1072 |
-
class
|
1073 |
value: list[a]
|
1074 |
|
1075 |
@staticmethod
|
1076 |
-
def id() -> "
|
1077 |
-
return
|
1078 |
|
1079 |
@staticmethod
|
1080 |
def compose(
|
1081 |
-
this: "
|
1082 |
-
) -> "
|
1083 |
-
return
|
1084 |
-
|
1085 |
|
|
|
|
|
|
|
|
|
1086 |
- **Identity**: The identity element is an empty list (`ListConcatenation([])`).
|
1087 |
- **Composition**: The composition of two lists is their concatenation (`this.value + other.value`).
|
|
|
|
|
1088 |
|
|
|
|
|
|
|
|
|
1089 |
### Category of Integer Addition
|
1090 |
|
1091 |
Now, let's define the category of integer addition:
|
|
|
|
|
1092 |
|
1093 |
-
|
|
|
|
|
1094 |
@dataclass
|
1095 |
class IntAddition:
|
1096 |
value: int
|
@@ -1102,102 +1089,95 @@ def _(md):
|
|
1102 |
@staticmethod
|
1103 |
def compose(this: "IntAddition", other: "IntAddition") -> "IntAddition":
|
1104 |
return IntAddition(this.value + other.value)
|
1105 |
-
|
1106 |
|
|
|
|
|
|
|
|
|
1107 |
- **Identity**: The identity element is `IntAddition(0)` (the additive identity).
|
1108 |
- **Composition**: The composition of two integers is their sum (`this.value + other.value`).
|
|
|
|
|
1109 |
|
|
|
|
|
|
|
|
|
1110 |
### Defining the Length Functor
|
1111 |
|
1112 |
We now define the `length` function as a functor, mapping from the category of list concatenation to the category of integer addition:
|
|
|
|
|
1113 |
|
1114 |
-
|
|
|
|
|
1115 |
length = lambda l: IntAddition(len(l.value))
|
1116 |
-
|
|
|
1117 |
|
|
|
|
|
|
|
1118 |
This function takes an instance of `ListConcatenation`, computes its length, and returns an `IntAddition` instance with the computed length.
|
|
|
|
|
|
|
1119 |
|
|
|
|
|
|
|
1120 |
### Verifying Functor Laws
|
1121 |
|
1122 |
Now, let’s verify that `length` satisfies the two functor laws.
|
1123 |
|
1124 |
#### 1. **Identity Law**:
|
1125 |
The identity law states that applying the functor to the identity element of one category should give the identity element of the other category.
|
1126 |
-
|
1127 |
-
```python
|
1128 |
-
>>> length(ListConcatenation.id()) == IntAddition.id()
|
1129 |
-
True
|
1130 |
-
```
|
1131 |
-
|
1132 |
-
This ensures that the length of an empty list (identity in the `ListConcatenation` category) is `0` (identity in the `IntAddition` category).
|
1133 |
-
|
1134 |
-
#### 2. **Composition Law**:
|
1135 |
-
The composition law states that the functor should preserve composition. Applying the functor to a composed element should be the same as composing the functor applied to the individual elements.
|
1136 |
-
|
1137 |
-
```python
|
1138 |
-
>>> length(ListConcatenation.compose(lista, listb)) == IntAddition.compose(
|
1139 |
-
>>> length(lista), length(listb)
|
1140 |
-
>>> )
|
1141 |
-
True
|
1142 |
-
```
|
1143 |
-
|
1144 |
-
This ensures that the length of the concatenation of two lists is the same as the sum of the lengths of the individual lists.
|
1145 |
""")
|
1146 |
return
|
1147 |
|
1148 |
|
1149 |
-
@app.cell
|
1150 |
-
def _(
|
1151 |
-
|
1152 |
-
|
1153 |
-
value: list[a]
|
1154 |
-
|
1155 |
-
@staticmethod
|
1156 |
-
def id() -> "ListConcatentation[a]":
|
1157 |
-
return ListConcatentation([])
|
1158 |
-
|
1159 |
-
@staticmethod
|
1160 |
-
def compose(
|
1161 |
-
this: "ListConcatentation[a]", other: "ListConcatentation[a]"
|
1162 |
-
) -> "ListConcatentation[a]":
|
1163 |
-
return ListConcatentation(this.value + other.value)
|
1164 |
-
|
1165 |
-
|
1166 |
-
@dataclass
|
1167 |
-
class IntAddition:
|
1168 |
-
value: int
|
1169 |
-
|
1170 |
-
@staticmethod
|
1171 |
-
def id() -> "IntAddition":
|
1172 |
-
return IntAddition(0)
|
1173 |
-
|
1174 |
-
@staticmethod
|
1175 |
-
def compose(this: "IntAddition", other: "IntAddition") -> "IntAddition":
|
1176 |
-
return IntAddition(this.value + other.value)
|
1177 |
-
return IntAddition, ListConcatentation
|
1178 |
|
1179 |
|
1180 |
@app.cell(hide_code=True)
|
1181 |
-
def _(
|
1182 |
-
|
1183 |
-
|
|
|
|
|
1184 |
|
1185 |
|
1186 |
@app.cell(hide_code=True)
|
1187 |
-
def _(
|
1188 |
-
|
|
|
|
|
|
|
1189 |
return
|
1190 |
|
1191 |
|
1192 |
-
@app.cell
|
1193 |
def _(IntAddition, ListConcatentation, length):
|
1194 |
-
|
1195 |
-
|
1196 |
|
1197 |
|
1198 |
-
length(ListConcatentation.compose(
|
1199 |
-
length(
|
1200 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1201 |
return
|
1202 |
|
1203 |
|
@@ -1243,23 +1223,13 @@ def _():
|
|
1243 |
@app.cell(hide_code=True)
|
1244 |
def _():
|
1245 |
from abc import abstractmethod, ABC
|
1246 |
-
|
1247 |
-
return ABC, abstractmethod, dataclass
|
1248 |
-
|
1249 |
-
|
1250 |
-
@app.cell(hide_code=True)
|
1251 |
-
def _():
|
1252 |
-
from typing import TypeVar, Generic
|
1253 |
-
from collections.abc import Callable
|
1254 |
-
return Callable, Generic, TypeVar
|
1255 |
|
1256 |
|
1257 |
@app.cell(hide_code=True)
|
1258 |
def _(TypeVar):
|
1259 |
-
a = TypeVar("a")
|
1260 |
-
b = TypeVar("b")
|
1261 |
c = TypeVar("c")
|
1262 |
-
return
|
1263 |
|
1264 |
|
1265 |
if __name__ == "__main__":
|
|
|
103 |
return
|
104 |
|
105 |
|
106 |
+
@app.cell
|
107 |
+
def _():
|
108 |
+
from dataclasses import dataclass
|
109 |
+
from typing import Callable, Generic, TypeVar
|
110 |
+
|
111 |
+
a = TypeVar("a")
|
112 |
+
b = TypeVar("b")
|
113 |
+
return Callable, Generic, TypeVar, a, b, dataclass
|
114 |
+
|
115 |
+
|
116 |
+
@app.cell
|
117 |
def _(Callable, Functor, Generic, a, b, dataclass):
|
118 |
@dataclass
|
119 |
class Wrapper(Functor, Generic[a]):
|
|
|
162 |
## The List Wrapper
|
163 |
|
164 |
We can define a `ListWrapper` class to represent a wrapped list that supports `fmap`:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
""")
|
166 |
return
|
167 |
|
168 |
|
169 |
+
@app.cell
|
170 |
def _(Callable, Functor, Generic, a, b, dataclass):
|
171 |
@dataclass
|
172 |
class ListWrapper(Functor, Generic[a]):
|
|
|
183 |
return ListWrapper, list_wrapper
|
184 |
|
185 |
|
186 |
+
@app.cell
|
187 |
+
def _(ListWrapper, mo):
|
188 |
+
with mo.redirect_stdout():
|
189 |
+
print(ListWrapper(value=[2, 3, 4, 5]))
|
190 |
+
print(ListWrapper(value=[[1], [2], [3], [4]]))
|
191 |
+
return
|
192 |
+
|
193 |
+
|
194 |
@app.cell(hide_code=True)
|
195 |
def _(md):
|
196 |
md("""
|
|
|
315 |
return
|
316 |
|
317 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
318 |
@app.cell(hide_code=True)
|
319 |
def _(Callable, Functor, Generic, a, b, dataclass, md):
|
320 |
@dataclass
|
|
|
362 |
return (RoseTree,)
|
363 |
|
364 |
|
365 |
+
@app.cell(hide_code=True)
|
366 |
+
def _(RoseTree, mo):
|
367 |
+
ftree = RoseTree(1, [RoseTree(2, []), RoseTree(3, [RoseTree(4, [])])])
|
368 |
+
|
369 |
+
with mo.redirect_stdout():
|
370 |
+
print(ftree)
|
371 |
+
print(ftree.fmap(lambda x: [x]))
|
372 |
+
print(ftree.fmap(lambda x: RoseTree(x, [])))
|
373 |
+
return (ftree,)
|
374 |
+
|
375 |
+
|
376 |
@app.cell(hide_code=True)
|
377 |
def _(md):
|
378 |
md("""
|
|
|
512 |
@app.cell(hide_code=True)
|
513 |
def _(md):
|
514 |
md("""
|
515 |
+
And here is an `EvilFunctor`. We can verify it's not a valid `Functor`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
516 |
""")
|
517 |
return
|
518 |
|
519 |
|
520 |
+
@app.cell
|
521 |
def _(Callable, Functor, Generic, a, b, dataclass):
|
522 |
@dataclass
|
523 |
class EvilFunctor(Functor, Generic[a]):
|
|
|
631 |
@app.cell(hide_code=True)
|
632 |
def _(md):
|
633 |
md("""
|
634 |
+
> Try with utility functions in the cell below
|
635 |
""")
|
636 |
return
|
637 |
|
|
|
660 |
One example is the **`Maybe`** type from Haskell, which is used to represent computations that can either result in a value (`Just a`) or no value (`Nothing`).
|
661 |
|
662 |
We can define the `Maybe` functor as below:
|
663 |
+
""")
|
664 |
+
return
|
665 |
|
666 |
+
|
667 |
+
@app.cell
|
668 |
+
def _(Callable, Functor, Generic, a, b, dataclass):
|
669 |
@dataclass
|
670 |
class Just(Generic[a]):
|
671 |
value: a
|
|
|
677 |
def __repr__(self):
|
678 |
return f"Just {self.value}"
|
679 |
|
680 |
+
|
681 |
@dataclass
|
682 |
class Maybe(Functor, Generic[a]):
|
683 |
value: None | Just[a]
|
684 |
|
685 |
def fmap(self, func: Callable[[a], b]) -> "Maybe[b]":
|
686 |
# Apply the function to the value inside `Just`, or return `Nothing` if value is None
|
687 |
+
return (
|
688 |
+
Maybe(Just(func(self.value.value))) if self.value else Maybe(None)
|
689 |
+
)
|
690 |
|
691 |
def __repr__(self):
|
692 |
return repr(self.value) if self.value else "Nothing"
|
693 |
+
return Just, Maybe
|
694 |
|
695 |
+
|
696 |
+
@app.cell(hide_code=True)
|
697 |
+
def _(md):
|
698 |
+
md("""
|
699 |
- **`Just`** is a wrapper that holds a value. We use it to represent the presence of a value.
|
700 |
- **`Maybe`** is a functor that can either hold a `Just` value or be `Nothing` (equivalent to `None` in Python). The `fmap` method applies a function to the value inside the `Just` wrapper, if it exists. If the value is `None` (representing `Nothing`), `fmap` simply returns `Nothing`.
|
701 |
|
|
|
706 |
return
|
707 |
|
708 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
709 |
@app.cell(hide_code=True)
|
710 |
def _(Just, Maybe, ftree, inc, mo):
|
711 |
with mo.redirect_stdout():
|
|
|
761 |
There are three laws that categories need to follow.
|
762 |
|
763 |
1. The composition of morphisms needs to be **associative**. Symbolically, $f ∘ (g ∘ h) = (f ∘ g) ∘ h$
|
764 |
+
|
765 |
- Morphisms are applied right to left, so with $f ∘ g$ first $g$ is applied, then $f$.
|
766 |
|
767 |
2. The category needs to be **closed** under the composition operation. So if $f : B → C$ and $g : A → B$, then there must be some morphism $h : A → C$ in the category such that $h = f ∘ g$.
|
|
|
1035 |
### Category of List Concatenation
|
1036 |
|
1037 |
First, let’s define the category of list concatenation:
|
1038 |
+
""")
|
1039 |
+
return
|
1040 |
|
1041 |
+
|
1042 |
+
@app.cell
|
1043 |
+
def _(Generic, a, dataclass):
|
1044 |
@dataclass
|
1045 |
+
class ListConcatentation(Generic[a]):
|
1046 |
value: list[a]
|
1047 |
|
1048 |
@staticmethod
|
1049 |
+
def id() -> "ListConcatentation[a]":
|
1050 |
+
return ListConcatentation([])
|
1051 |
|
1052 |
@staticmethod
|
1053 |
def compose(
|
1054 |
+
this: "ListConcatentation[a]", other: "ListConcatentation[a]"
|
1055 |
+
) -> "ListConcatentation[a]":
|
1056 |
+
return ListConcatentation(this.value + other.value)
|
1057 |
+
return (ListConcatentation,)
|
1058 |
|
1059 |
+
|
1060 |
+
@app.cell(hide_code=True)
|
1061 |
+
def _(md):
|
1062 |
+
md("""
|
1063 |
- **Identity**: The identity element is an empty list (`ListConcatenation([])`).
|
1064 |
- **Composition**: The composition of two lists is their concatenation (`this.value + other.value`).
|
1065 |
+
""")
|
1066 |
+
return
|
1067 |
|
1068 |
+
|
1069 |
+
@app.cell(hide_code=True)
|
1070 |
+
def _(md):
|
1071 |
+
md("""
|
1072 |
### Category of Integer Addition
|
1073 |
|
1074 |
Now, let's define the category of integer addition:
|
1075 |
+
""")
|
1076 |
+
return
|
1077 |
|
1078 |
+
|
1079 |
+
@app.cell
|
1080 |
+
def _(dataclass):
|
1081 |
@dataclass
|
1082 |
class IntAddition:
|
1083 |
value: int
|
|
|
1089 |
@staticmethod
|
1090 |
def compose(this: "IntAddition", other: "IntAddition") -> "IntAddition":
|
1091 |
return IntAddition(this.value + other.value)
|
1092 |
+
return (IntAddition,)
|
1093 |
|
1094 |
+
|
1095 |
+
@app.cell(hide_code=True)
|
1096 |
+
def _(md):
|
1097 |
+
md("""
|
1098 |
- **Identity**: The identity element is `IntAddition(0)` (the additive identity).
|
1099 |
- **Composition**: The composition of two integers is their sum (`this.value + other.value`).
|
1100 |
+
""")
|
1101 |
+
return
|
1102 |
|
1103 |
+
|
1104 |
+
@app.cell(hide_code=True)
|
1105 |
+
def _(md):
|
1106 |
+
md("""
|
1107 |
### Defining the Length Functor
|
1108 |
|
1109 |
We now define the `length` function as a functor, mapping from the category of list concatenation to the category of integer addition:
|
1110 |
+
""")
|
1111 |
+
return
|
1112 |
|
1113 |
+
|
1114 |
+
@app.cell
|
1115 |
+
def _(IntAddition):
|
1116 |
length = lambda l: IntAddition(len(l.value))
|
1117 |
+
return (length,)
|
1118 |
+
|
1119 |
|
1120 |
+
@app.cell(hide_code=True)
|
1121 |
+
def _(md):
|
1122 |
+
md("""
|
1123 |
This function takes an instance of `ListConcatenation`, computes its length, and returns an `IntAddition` instance with the computed length.
|
1124 |
+
""")
|
1125 |
+
return
|
1126 |
+
|
1127 |
|
1128 |
+
@app.cell(hide_code=True)
|
1129 |
+
def _(md):
|
1130 |
+
md("""
|
1131 |
### Verifying Functor Laws
|
1132 |
|
1133 |
Now, let’s verify that `length` satisfies the two functor laws.
|
1134 |
|
1135 |
#### 1. **Identity Law**:
|
1136 |
The identity law states that applying the functor to the identity element of one category should give the identity element of the other category.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1137 |
""")
|
1138 |
return
|
1139 |
|
1140 |
|
1141 |
+
@app.cell
|
1142 |
+
def _(IntAddition, ListConcatentation, length):
|
1143 |
+
length(ListConcatentation.id()) == IntAddition.id()
|
1144 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1145 |
|
1146 |
|
1147 |
@app.cell(hide_code=True)
|
1148 |
+
def _(md):
|
1149 |
+
md("""
|
1150 |
+
This ensures that the length of an empty list (identity in the `ListConcatenation` category) is `0` (identity in the `IntAddition` category).
|
1151 |
+
""")
|
1152 |
+
return
|
1153 |
|
1154 |
|
1155 |
@app.cell(hide_code=True)
|
1156 |
+
def _(md):
|
1157 |
+
md("""
|
1158 |
+
#### 2. **Composition Law**:
|
1159 |
+
The composition law states that the functor should preserve composition. Applying the functor to a composed element should be the same as composing the functor applied to the individual elements.
|
1160 |
+
""")
|
1161 |
return
|
1162 |
|
1163 |
|
1164 |
+
@app.cell
|
1165 |
def _(IntAddition, ListConcatentation, length):
|
1166 |
+
lista = ListConcatentation([1, 2])
|
1167 |
+
listb = ListConcatentation([3, 4])
|
1168 |
|
1169 |
|
1170 |
+
length(ListConcatentation.compose(lista, listb)) == IntAddition.compose(
|
1171 |
+
length(lista), length(listb)
|
1172 |
)
|
1173 |
+
return lista, listb
|
1174 |
+
|
1175 |
+
|
1176 |
+
@app.cell(hide_code=True)
|
1177 |
+
def _(md):
|
1178 |
+
md("""
|
1179 |
+
This ensures that the length of the concatenation of two lists is the same as the sum of the lengths of the individual lists.
|
1180 |
+
""")
|
1181 |
return
|
1182 |
|
1183 |
|
|
|
1223 |
@app.cell(hide_code=True)
|
1224 |
def _():
|
1225 |
from abc import abstractmethod, ABC
|
1226 |
+
return ABC, abstractmethod
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1227 |
|
1228 |
|
1229 |
@app.cell(hide_code=True)
|
1230 |
def _(TypeVar):
|
|
|
|
|
1231 |
c = TypeVar("c")
|
1232 |
+
return (c,)
|
1233 |
|
1234 |
|
1235 |
if __name__ == "__main__":
|