Spaces:
Sleeping
Sleeping
Commit
·
6399834
1
Parent(s):
397edde
refactor(functors): `v0.1.5` of `functors.py`
Browse files
functional_programming/05_functors.py
CHANGED
|
@@ -7,8 +7,12 @@
|
|
| 7 |
|
| 8 |
import marimo
|
| 9 |
|
| 10 |
-
__generated_with = "0.12.
|
| 11 |
-
app = marimo.App(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
@app.cell(hide_code=True)
|
| 14 |
def _(mo):
|
|
@@ -36,7 +40,7 @@ def _(mo):
|
|
| 36 |
/// details | Notebook metadata
|
| 37 |
type: info
|
| 38 |
|
| 39 |
-
version: 0.1.
|
| 40 |
reviewer: [Haleshot](https://github.com/Haleshot)
|
| 41 |
|
| 42 |
///
|
|
@@ -354,7 +358,7 @@ def _(mo):
|
|
| 354 |
|
| 355 |
`fmap` for `Either` will ignore Left values, but will apply the supplied function to values contained in the Right.
|
| 356 |
|
| 357 |
-
The implementation is:
|
| 358 |
"""
|
| 359 |
)
|
| 360 |
return
|
|
@@ -707,7 +711,7 @@ def _(ABC, B, Callable, abstractmethod, dataclass):
|
|
| 707 |
@classmethod
|
| 708 |
@abstractmethod
|
| 709 |
def fmap(cls, g: Callable[[A], B], fa: "Functor[A]") -> "Functor[B]":
|
| 710 |
-
|
| 711 |
|
| 712 |
@classmethod
|
| 713 |
def const(cls, fa: "Functor[A]", b: B) -> "Functor[B]":
|
|
@@ -912,8 +916,13 @@ def _(mo):
|
|
| 912 |
|
| 913 |
Once again there are a few axioms that functors have to obey.
|
| 914 |
|
| 915 |
-
1. Given an identity morphism $id_A$ on an object $A$, $F ( id_A )$ must be the identity morphism on $F ( A )
|
| 916 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 917 |
"""
|
| 918 |
)
|
| 919 |
return
|
|
@@ -1205,6 +1214,187 @@ def _(mo):
|
|
| 1205 |
return
|
| 1206 |
|
| 1207 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1208 |
@app.cell(hide_code=True)
|
| 1209 |
def _(mo):
|
| 1210 |
mo.md(
|
|
@@ -1253,7 +1443,8 @@ def _(TypeVar):
|
|
| 1253 |
A = TypeVar("A")
|
| 1254 |
B = TypeVar("B")
|
| 1255 |
C = TypeVar("C")
|
| 1256 |
-
|
|
|
|
| 1257 |
|
| 1258 |
|
| 1259 |
if __name__ == "__main__":
|
|
|
|
| 7 |
|
| 8 |
import marimo
|
| 9 |
|
| 10 |
+
__generated_with = "0.12.8"
|
| 11 |
+
app = marimo.App(
|
| 12 |
+
app_title="Category Theory and Functors",
|
| 13 |
+
css_file="/Users/chanhuizhihou/Library/Application Support/mtheme/themes/gruvbox.css",
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
|
| 17 |
@app.cell(hide_code=True)
|
| 18 |
def _(mo):
|
|
|
|
| 40 |
/// details | Notebook metadata
|
| 41 |
type: info
|
| 42 |
|
| 43 |
+
version: 0.1.5 | last modified: 2025-04-11 | author: [métaboulie](https://github.com/metaboulie)<br/>
|
| 44 |
reviewer: [Haleshot](https://github.com/Haleshot)
|
| 45 |
|
| 46 |
///
|
|
|
|
| 358 |
|
| 359 |
`fmap` for `Either` will ignore Left values, but will apply the supplied function to values contained in the Right.
|
| 360 |
|
| 361 |
+
The implementation is:
|
| 362 |
"""
|
| 363 |
)
|
| 364 |
return
|
|
|
|
| 711 |
@classmethod
|
| 712 |
@abstractmethod
|
| 713 |
def fmap(cls, g: Callable[[A], B], fa: "Functor[A]") -> "Functor[B]":
|
| 714 |
+
raise NotImplementedError("Subclasses must implement fmap")
|
| 715 |
|
| 716 |
@classmethod
|
| 717 |
def const(cls, fa: "Functor[A]", b: B) -> "Functor[B]":
|
|
|
|
| 916 |
|
| 917 |
Once again there are a few axioms that functors have to obey.
|
| 918 |
|
| 919 |
+
1. Given an identity morphism $id_A$ on an object $A$, $F ( id_A )$ must be the identity morphism on $F ( A )$.:
|
| 920 |
+
|
| 921 |
+
$$F({id} _{A})={id} _{F(A)}$$
|
| 922 |
+
|
| 923 |
+
3. Functors must distribute over morphism composition.
|
| 924 |
+
|
| 925 |
+
$$F(f\circ g)=F(f)\circ F(g)$$
|
| 926 |
"""
|
| 927 |
)
|
| 928 |
return
|
|
|
|
| 1214 |
return
|
| 1215 |
|
| 1216 |
|
| 1217 |
+
@app.cell(hide_code=True)
|
| 1218 |
+
def _(mo):
|
| 1219 |
+
mo.md(
|
| 1220 |
+
r"""
|
| 1221 |
+
# Bifunctor
|
| 1222 |
+
|
| 1223 |
+
A `Bifunctor` is a type constructor that takes two type arguments and **is a functor in both arguments.**
|
| 1224 |
+
|
| 1225 |
+
For example, think about `Either`'s usual `Functor` instance. It only allows you to fmap over the second type parameter: `right` values get mapped, `left` values stay as they are.
|
| 1226 |
+
|
| 1227 |
+
However, its `Bifunctor` instance allows you to map both halves of the sum.
|
| 1228 |
+
|
| 1229 |
+
There are three core methods for `Bifunctor`:
|
| 1230 |
+
|
| 1231 |
+
- `bimap` allows mapping over both type arguments at once.
|
| 1232 |
+
- `first` and `second` are also provided for mapping over only one type argument at a time.
|
| 1233 |
+
|
| 1234 |
+
|
| 1235 |
+
The abstraction of `Bifunctor` is:
|
| 1236 |
+
"""
|
| 1237 |
+
)
|
| 1238 |
+
return
|
| 1239 |
+
|
| 1240 |
+
|
| 1241 |
+
@app.cell
|
| 1242 |
+
def _(ABC, B, Callable, D, dataclass, f, id):
|
| 1243 |
+
@dataclass
|
| 1244 |
+
class Bifunctor[A, C](ABC):
|
| 1245 |
+
@classmethod
|
| 1246 |
+
def bimap(
|
| 1247 |
+
cls, g: Callable[[A], B], h: Callable[[C], D], fa: "Bifunctor[A, C]"
|
| 1248 |
+
) -> "Bifunctor[B, D]":
|
| 1249 |
+
return cls.first(f, cls.second(g, fa))
|
| 1250 |
+
|
| 1251 |
+
@classmethod
|
| 1252 |
+
def first(
|
| 1253 |
+
cls, g: Callable[[A], B], fa: "Bifunctor[A, C]"
|
| 1254 |
+
) -> "Bifunctor[B, C]":
|
| 1255 |
+
return cls.bimap(g, id, fa)
|
| 1256 |
+
|
| 1257 |
+
@classmethod
|
| 1258 |
+
def second(
|
| 1259 |
+
cls, g: Callable[[B], C], fa: "Bifunctor[A, B]"
|
| 1260 |
+
) -> "Bifunctor[A, C]":
|
| 1261 |
+
return cls.bimap(id, g, fa)
|
| 1262 |
+
return (Bifunctor,)
|
| 1263 |
+
|
| 1264 |
+
|
| 1265 |
+
@app.cell(hide_code=True)
|
| 1266 |
+
def _(mo):
|
| 1267 |
+
mo.md(
|
| 1268 |
+
r"""
|
| 1269 |
+
/// admonition | minimal implementation requirement
|
| 1270 |
+
- `bimap` or both `first` and `second`
|
| 1271 |
+
///
|
| 1272 |
+
"""
|
| 1273 |
+
)
|
| 1274 |
+
return
|
| 1275 |
+
|
| 1276 |
+
|
| 1277 |
+
@app.cell(hide_code=True)
|
| 1278 |
+
def _(mo):
|
| 1279 |
+
mo.md(r"""## Instances of Bifunctor""")
|
| 1280 |
+
return
|
| 1281 |
+
|
| 1282 |
+
|
| 1283 |
+
@app.cell(hide_code=True)
|
| 1284 |
+
def _(mo):
|
| 1285 |
+
mo.md(
|
| 1286 |
+
r"""
|
| 1287 |
+
### The Either Bifunctor
|
| 1288 |
+
|
| 1289 |
+
For the `Either Bifunctor`, we allow it to map a function over the `left` value as well.
|
| 1290 |
+
|
| 1291 |
+
Notice that, the `Either Bifunctor` still only contains the `left` value or the `right` value.
|
| 1292 |
+
"""
|
| 1293 |
+
)
|
| 1294 |
+
return
|
| 1295 |
+
|
| 1296 |
+
|
| 1297 |
+
@app.cell
|
| 1298 |
+
def _(B, Bifunctor, Callable, D, dataclass):
|
| 1299 |
+
@dataclass
|
| 1300 |
+
class BiEither[A, C](Bifunctor):
|
| 1301 |
+
left: A = None
|
| 1302 |
+
right: C = None
|
| 1303 |
+
|
| 1304 |
+
def __post_init__(self):
|
| 1305 |
+
if (self.left is not None and self.right is not None) or (
|
| 1306 |
+
self.left is None and self.right is None
|
| 1307 |
+
):
|
| 1308 |
+
raise TypeError(
|
| 1309 |
+
"Provide either the value of the left or the value of the right."
|
| 1310 |
+
)
|
| 1311 |
+
|
| 1312 |
+
@classmethod
|
| 1313 |
+
def bimap(
|
| 1314 |
+
cls, g: Callable[[A], B], h: Callable[[C], D], fa: "BiEither[A, C]"
|
| 1315 |
+
) -> "BiEither[B, D]":
|
| 1316 |
+
if fa.left is not None:
|
| 1317 |
+
return cls(left=g(fa.left))
|
| 1318 |
+
return cls(right=h(fa.right))
|
| 1319 |
+
|
| 1320 |
+
def __repr__(self):
|
| 1321 |
+
if self.left is not None:
|
| 1322 |
+
return f"Left({self.left!r})"
|
| 1323 |
+
return f"Right({self.right!r})"
|
| 1324 |
+
return (BiEither,)
|
| 1325 |
+
|
| 1326 |
+
|
| 1327 |
+
@app.cell
|
| 1328 |
+
def _(BiEither):
|
| 1329 |
+
print(BiEither.bimap(lambda x: x + 1, lambda x: x * 2, BiEither(left=1)))
|
| 1330 |
+
print(BiEither.bimap(lambda x: x + 1, lambda x: x * 2, BiEither(right=2)))
|
| 1331 |
+
print(BiEither.first(lambda x: x + 1, BiEither(left=1)))
|
| 1332 |
+
print(BiEither.first(lambda x: x + 1, BiEither(right=2)))
|
| 1333 |
+
print(BiEither.second(lambda x: x + 1, BiEither(left=1)))
|
| 1334 |
+
print(BiEither.second(lambda x: x + 1, BiEither(right=2)))
|
| 1335 |
+
return
|
| 1336 |
+
|
| 1337 |
+
|
| 1338 |
+
@app.cell(hide_code=True)
|
| 1339 |
+
def _(mo):
|
| 1340 |
+
mo.md(
|
| 1341 |
+
r"""
|
| 1342 |
+
### The 2d Tuple Bifunctor
|
| 1343 |
+
|
| 1344 |
+
For 2d tuples, we simply expect `bimap` to map 2 functions to the 2 elements in the tuple respectively.
|
| 1345 |
+
"""
|
| 1346 |
+
)
|
| 1347 |
+
return
|
| 1348 |
+
|
| 1349 |
+
|
| 1350 |
+
@app.cell
|
| 1351 |
+
def _(B, Bifunctor, Callable, D, dataclass):
|
| 1352 |
+
@dataclass
|
| 1353 |
+
class BiTuple[A, C](Bifunctor):
|
| 1354 |
+
value: tuple[A, C]
|
| 1355 |
+
|
| 1356 |
+
@classmethod
|
| 1357 |
+
def bimap(
|
| 1358 |
+
cls, g: Callable[[A], B], h: Callable[[C], D], fa: "BiTuple[A, C]"
|
| 1359 |
+
) -> "BiTuple[B, D]":
|
| 1360 |
+
return cls((g(fa.value[0]), h(fa.value[1])))
|
| 1361 |
+
return (BiTuple,)
|
| 1362 |
+
|
| 1363 |
+
|
| 1364 |
+
@app.cell
|
| 1365 |
+
def _(BiTuple):
|
| 1366 |
+
print(BiTuple.bimap(lambda x: x + 1, lambda x: x * 2, BiTuple((1, 2))))
|
| 1367 |
+
print(BiTuple.first(lambda x: x + 1, BiTuple((1, 2))))
|
| 1368 |
+
print(BiTuple.second(lambda x: x + 1, BiTuple((1, 2))))
|
| 1369 |
+
return
|
| 1370 |
+
|
| 1371 |
+
|
| 1372 |
+
@app.cell(hide_code=True)
|
| 1373 |
+
def _(mo):
|
| 1374 |
+
mo.md(
|
| 1375 |
+
r"""
|
| 1376 |
+
## Bifunctor laws
|
| 1377 |
+
|
| 1378 |
+
The only law we need to follow is
|
| 1379 |
+
|
| 1380 |
+
```python
|
| 1381 |
+
bimap(id, id, fa) == id(fa)
|
| 1382 |
+
```
|
| 1383 |
+
|
| 1384 |
+
and then other laws are followed automatically.
|
| 1385 |
+
"""
|
| 1386 |
+
)
|
| 1387 |
+
return
|
| 1388 |
+
|
| 1389 |
+
|
| 1390 |
+
@app.cell
|
| 1391 |
+
def _(BiEither, BiTuple, id):
|
| 1392 |
+
print(BiEither.bimap(id, id, BiEither(left=1)) == id(BiEither(left=1)))
|
| 1393 |
+
print(BiEither.bimap(id, id, BiEither(right=1)) == id(BiEither(right=1)))
|
| 1394 |
+
print(BiTuple.bimap(id, id, BiTuple((1, 2))) == id(BiTuple((1, 2))))
|
| 1395 |
+
return
|
| 1396 |
+
|
| 1397 |
+
|
| 1398 |
@app.cell(hide_code=True)
|
| 1399 |
def _(mo):
|
| 1400 |
mo.md(
|
|
|
|
| 1443 |
A = TypeVar("A")
|
| 1444 |
B = TypeVar("B")
|
| 1445 |
C = TypeVar("C")
|
| 1446 |
+
D = TypeVar("D")
|
| 1447 |
+
return A, B, C, D
|
| 1448 |
|
| 1449 |
|
| 1450 |
if __name__ == "__main__":
|
functional_programming/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
| 1 |
# Changelog of the functional-programming course
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
## 2025-04-08
|
| 4 |
|
| 5 |
**functors.py**
|
|
|
|
| 1 |
# Changelog of the functional-programming course
|
| 2 |
|
| 3 |
+
## 2025-04-11
|
| 4 |
+
|
| 5 |
+
**functors.py**
|
| 6 |
+
|
| 7 |
+
+ add `Bifunctor` section
|
| 8 |
+
* replace `return NotImplementedError` with `raise NotImplementedError`
|
| 9 |
+
|
| 10 |
## 2025-04-08
|
| 11 |
|
| 12 |
**functors.py**
|
functional_programming/README.md
CHANGED
|
@@ -3,20 +3,20 @@
|
|
| 3 |
_🚧 This collection is a [work in progress](https://github.com/marimo-team/learn/issues/51)._
|
| 4 |
|
| 5 |
This series of marimo notebooks introduces the powerful paradigm of functional
|
| 6 |
-
programming through Python. Taking inspiration from Haskell and Category
|
| 7 |
-
we'll build a strong foundation in FP concepts that can transform how
|
| 8 |
-
approach software development.
|
| 9 |
|
| 10 |
## What You'll Learn
|
| 11 |
|
| 12 |
-
**Using only Python's standard library**, we'll construct functional
|
| 13 |
-
concepts from first principles.
|
| 14 |
|
| 15 |
Topics include:
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
|
| 21 |
## Running Notebooks
|
| 22 |
|
|
@@ -24,20 +24,21 @@ Topics include:
|
|
| 24 |
|
| 25 |
To run a notebook locally, use
|
| 26 |
|
| 27 |
-
```bash
|
| 28 |
-
uvx marimo edit <URL>
|
| 29 |
```
|
| 30 |
|
| 31 |
For example, run the `Functor` tutorial with
|
| 32 |
|
| 33 |
-
```bash
|
| 34 |
uvx marimo edit https://github.com/marimo-team/learn/blob/main/functional_programming/05_functors.py
|
| 35 |
```
|
| 36 |
|
| 37 |
### On Our Online Playground
|
| 38 |
|
| 39 |
You can also open notebooks in our online playground by appending `marimo.app/` to a notebook's URL like:
|
| 40 |
-
|
|
|
|
| 41 |
|
| 42 |
### On Our Landing Page
|
| 43 |
|
|
@@ -50,17 +51,19 @@ on Discord (@eugene.hs).
|
|
| 50 |
|
| 51 |
# Description of notebooks
|
| 52 |
|
| 53 |
-
Check [here](https://github.com/marimo-team/learn/issues/51) for current series
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
|
| 57 |
-
|
|
|
|
|
|
|
| 58 |
|
| 59 |
**Authors.**
|
| 60 |
|
| 61 |
Thanks to all our notebook authors!
|
| 62 |
|
| 63 |
-
-
|
| 64 |
|
| 65 |
**Reviewers.**
|
| 66 |
|
|
|
|
| 3 |
_🚧 This collection is a [work in progress](https://github.com/marimo-team/learn/issues/51)._
|
| 4 |
|
| 5 |
This series of marimo notebooks introduces the powerful paradigm of functional
|
| 6 |
+
programming through Python. Taking inspiration from Haskell and Category
|
| 7 |
+
Theory, we'll build a strong foundation in FP concepts that can transform how
|
| 8 |
+
you approach software development.
|
| 9 |
|
| 10 |
## What You'll Learn
|
| 11 |
|
| 12 |
+
**Using only Python's standard library**, we'll construct functional
|
| 13 |
+
programming concepts from first principles.
|
| 14 |
|
| 15 |
Topics include:
|
| 16 |
|
| 17 |
+
+ Currying and higher-order functions
|
| 18 |
+
+ Functors, Applicatives, and Monads
|
| 19 |
+
+ Category theory fundamentals
|
| 20 |
|
| 21 |
## Running Notebooks
|
| 22 |
|
|
|
|
| 24 |
|
| 25 |
To run a notebook locally, use
|
| 26 |
|
| 27 |
+
```bash
|
| 28 |
+
uvx marimo edit <URL>
|
| 29 |
```
|
| 30 |
|
| 31 |
For example, run the `Functor` tutorial with
|
| 32 |
|
| 33 |
+
```bash
|
| 34 |
uvx marimo edit https://github.com/marimo-team/learn/blob/main/functional_programming/05_functors.py
|
| 35 |
```
|
| 36 |
|
| 37 |
### On Our Online Playground
|
| 38 |
|
| 39 |
You can also open notebooks in our online playground by appending `marimo.app/` to a notebook's URL like:
|
| 40 |
+
|
| 41 |
+
https://marimo.app/https://github.com/marimo-team/learn/blob/main/functional_programming/05_functors.py
|
| 42 |
|
| 43 |
### On Our Landing Page
|
| 44 |
|
|
|
|
| 51 |
|
| 52 |
# Description of notebooks
|
| 53 |
|
| 54 |
+
Check [here](https://github.com/marimo-team/learn/issues/51) for current series
|
| 55 |
+
structure.
|
| 56 |
+
|
| 57 |
+
| Notebook | Title | Key Concepts | Prerequisites |
|
| 58 |
+
|----------|-------|--------------|---------------|
|
| 59 |
+
| [05. Functors](https://github.com/marimo-team/learn/blob/main/functional_programming/05_functors.py) | Category Theory and Functors | Category Theory, Functors, fmap | Basic Python, Functions |
|
| 60 |
+
| [06. Applicatives](https://github.com/marimo-team/learn/blob/main/functional_programming/06_applicatives.py) | Applicative programming with effects | Applicative Functors, pure, apply, Effectful programming, Alternatives | Functors |
|
| 61 |
|
| 62 |
**Authors.**
|
| 63 |
|
| 64 |
Thanks to all our notebook authors!
|
| 65 |
|
| 66 |
+
- [métaboulie](https://github.com/metaboulie)
|
| 67 |
|
| 68 |
**Reviewers.**
|
| 69 |
|