Spaces:
Running
Running
Commit
·
8e62b80
1
Parent(s):
3c4c5bc
refactor(functors): `v0.1.4` of notebook `functors` for fp course
Browse files* move `Maybe` funtor to section `More Functor instances`
+ add `Either` functor
+ add `unzip` utility function for functors
functional_programming/05_functors.py
CHANGED
@@ -40,7 +40,7 @@ def _(mo):
|
|
40 |
/// details | Notebook metadata
|
41 |
type: info
|
42 |
|
43 |
-
version: 0.1.
|
44 |
reviewer: [Haleshot](https://github.com/Haleshot)
|
45 |
|
46 |
///
|
@@ -293,7 +293,21 @@ def _(mo):
|
|
293 |
def _(mo):
|
294 |
mo.md(
|
295 |
r"""
|
296 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
297 |
|
298 |
**`Maybe`** is a functor that can either hold a value (`Just(value)`) or be `Nothing` (equivalent to `None` in Python).
|
299 |
|
@@ -304,7 +318,7 @@ def _(mo):
|
|
304 |
By using `Maybe` as a functor, we gain the ability to apply transformations (`fmap`) to potentially absent values, without having to explicitly handle the `None` case every time.
|
305 |
///
|
306 |
|
307 |
-
We can implement the `Maybe` functor as:
|
308 |
"""
|
309 |
)
|
310 |
return
|
@@ -336,21 +350,62 @@ def _(Maybe, pp):
|
|
336 |
def _(mo):
|
337 |
mo.md(
|
338 |
r"""
|
339 |
-
##
|
340 |
|
341 |
-
|
342 |
|
343 |
-
The
|
|
|
|
|
|
|
|
|
344 |
"""
|
345 |
)
|
346 |
return
|
347 |
|
348 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
349 |
@app.cell(hide_code=True)
|
350 |
def _(mo):
|
351 |
mo.md(
|
352 |
"""
|
353 |
-
|
354 |
|
355 |
A **RoseTree** is a tree where:
|
356 |
|
@@ -363,7 +418,7 @@ def _(mo):
|
|
363 |
- File system directories
|
364 |
- Recursive computations
|
365 |
|
366 |
-
The implementation is:
|
367 |
"""
|
368 |
)
|
369 |
return
|
@@ -498,7 +553,7 @@ def _(mo):
|
|
498 |
def _(mo):
|
499 |
mo.md(
|
500 |
r"""
|
501 |
-
### Functor
|
502 |
|
503 |
We can define `id` and `compose` in `Python` as:
|
504 |
"""
|
@@ -576,11 +631,59 @@ def _(mo):
|
|
576 |
r"""
|
577 |
## Utility functions
|
578 |
|
579 |
-
|
580 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
581 |
|
582 |
-
|
583 |
-
|
|
|
|
|
|
|
|
|
|
|
584 |
"""
|
585 |
)
|
586 |
return
|
@@ -617,6 +720,12 @@ def _(ABC, B, Callable, abstractmethod, dataclass):
|
|
617 |
@classmethod
|
618 |
def void(cls, fa: "Functor[A]") -> "Functor[None]":
|
619 |
return cls.const(fa, None)
|
|
|
|
|
|
|
|
|
|
|
|
|
620 |
return (Functor,)
|
621 |
|
622 |
|
@@ -1138,9 +1247,9 @@ def _():
|
|
1138 |
@app.cell(hide_code=True)
|
1139 |
def _():
|
1140 |
from dataclasses import dataclass
|
1141 |
-
from typing import Callable, TypeVar
|
1142 |
from pprint import pp
|
1143 |
-
return Callable, TypeVar, dataclass, pp
|
1144 |
|
1145 |
|
1146 |
@app.cell(hide_code=True)
|
|
|
40 |
/// details | Notebook metadata
|
41 |
type: info
|
42 |
|
43 |
+
version: 0.1.4 | last modified: 2025-04-08 | author: [métaboulie](https://github.com/metaboulie)<br/>
|
44 |
reviewer: [Haleshot](https://github.com/Haleshot)
|
45 |
|
46 |
///
|
|
|
293 |
def _(mo):
|
294 |
mo.md(
|
295 |
r"""
|
296 |
+
# More Functor instances (optional)
|
297 |
+
|
298 |
+
In this section, we will explore more *Functor* instances to help you build up a better comprehension.
|
299 |
+
|
300 |
+
The main reference is [Data.Functor](https://hackage.haskell.org/package/base-4.21.0.0/docs/Data-Functor.html)
|
301 |
+
"""
|
302 |
+
)
|
303 |
+
return
|
304 |
+
|
305 |
+
|
306 |
+
@app.cell(hide_code=True)
|
307 |
+
def _(mo):
|
308 |
+
mo.md(
|
309 |
+
r"""
|
310 |
+
## The [Maybe](https://hackage.haskell.org/package/base-4.21.0.0/docs/Data-Maybe.html#t:Maybe) Functor
|
311 |
|
312 |
**`Maybe`** is a functor that can either hold a value (`Just(value)`) or be `Nothing` (equivalent to `None` in Python).
|
313 |
|
|
|
318 |
By using `Maybe` as a functor, we gain the ability to apply transformations (`fmap`) to potentially absent values, without having to explicitly handle the `None` case every time.
|
319 |
///
|
320 |
|
321 |
+
We can implement the `Maybe` functor as:
|
322 |
"""
|
323 |
)
|
324 |
return
|
|
|
350 |
def _(mo):
|
351 |
mo.md(
|
352 |
r"""
|
353 |
+
## The [Either](https://hackage.haskell.org/package/base-4.21.0.0/docs/Data-Either.html#t:Either) Functor
|
354 |
|
355 |
+
The `Either` type represents values with two possibilities: a value of type `Either a b` is either `Left a` or `Right b`.
|
356 |
|
357 |
+
The `Either` type is sometimes used to represent a value which is **either correct or an error**; by convention, the `left` attribute is used to hold an error value and the `right` attribute is used to hold a correct value.
|
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
|
365 |
|
366 |
|
367 |
+
@app.cell
|
368 |
+
def _(B, Callable, Functor, Union, dataclass):
|
369 |
+
@dataclass
|
370 |
+
class Either[A](Functor):
|
371 |
+
left: A = None
|
372 |
+
right: A = None
|
373 |
+
|
374 |
+
def __post_init__(self):
|
375 |
+
if (self.left is not None and self.right is not None) or (
|
376 |
+
self.left is None and self.right is None
|
377 |
+
):
|
378 |
+
raise TypeError(
|
379 |
+
"Provide either the value of the left or the value of the right."
|
380 |
+
)
|
381 |
+
|
382 |
+
@classmethod
|
383 |
+
def fmap(
|
384 |
+
cls, g: Callable[[A], B], fa: "Either[A]"
|
385 |
+
) -> Union["Either[A]", "Either[B]"]:
|
386 |
+
if fa.left is not None:
|
387 |
+
return cls(left=fa.left)
|
388 |
+
return cls(right=g(fa.right))
|
389 |
+
|
390 |
+
def __repr__(self):
|
391 |
+
if self.left is not None:
|
392 |
+
return f"Left({self.left!r})"
|
393 |
+
return f"Right({self.right!r})"
|
394 |
+
return (Either,)
|
395 |
+
|
396 |
+
|
397 |
+
@app.cell
|
398 |
+
def _(Either):
|
399 |
+
print(Either.fmap(lambda x: x + 1, Either(left=TypeError("Parse Error"))))
|
400 |
+
print(Either.fmap(lambda x: x + 1, Either(right=1)))
|
401 |
+
return
|
402 |
+
|
403 |
+
|
404 |
@app.cell(hide_code=True)
|
405 |
def _(mo):
|
406 |
mo.md(
|
407 |
"""
|
408 |
+
## The [RoseTree](https://en.wikipedia.org/wiki/Rose_tree) Functor
|
409 |
|
410 |
A **RoseTree** is a tree where:
|
411 |
|
|
|
418 |
- File system directories
|
419 |
- Recursive computations
|
420 |
|
421 |
+
The implementation is:
|
422 |
"""
|
423 |
)
|
424 |
return
|
|
|
553 |
def _(mo):
|
554 |
mo.md(
|
555 |
r"""
|
556 |
+
### Functor laws verification
|
557 |
|
558 |
We can define `id` and `compose` in `Python` as:
|
559 |
"""
|
|
|
631 |
r"""
|
632 |
## Utility functions
|
633 |
|
634 |
+
```python
|
635 |
+
@classmethod
|
636 |
+
def const(cls, fa: "Functor[A]", b: B) -> "Functor[B]":
|
637 |
+
return cls.fmap(lambda _: b, fa)
|
638 |
+
|
639 |
+
@classmethod
|
640 |
+
def void(cls, fa: "Functor[A]") -> "Functor[None]":
|
641 |
+
return cls.const(fa, None)
|
642 |
+
|
643 |
+
@classmethod
|
644 |
+
def unzip(
|
645 |
+
cls, fab: "Functor[tuple[A, B]]"
|
646 |
+
) -> tuple["Functor[A]", "Functor[B]"]:
|
647 |
+
return cls.fmap(lambda p: p[0], fab), cls.fmap(lambda p: p[1], fab)
|
648 |
+
```
|
649 |
+
|
650 |
+
- `const` replaces all values inside a functor with a constant `b`
|
651 |
+
- `void` is equivalent to `const(fa, None)`, transforming all values in a functor into `None`
|
652 |
+
- `unzip` is a generalization of the regular *unzip* on a list of pairs
|
653 |
+
"""
|
654 |
+
)
|
655 |
+
return
|
656 |
+
|
657 |
+
|
658 |
+
@app.cell
|
659 |
+
def _(List, Maybe):
|
660 |
+
print(Maybe.const(Maybe(0), 1))
|
661 |
+
print(Maybe.const(Maybe(None), 1))
|
662 |
+
print(List.const(List([1, 2, 3, 4]), 1))
|
663 |
+
return
|
664 |
+
|
665 |
+
|
666 |
+
@app.cell
|
667 |
+
def _(List, Maybe):
|
668 |
+
print(Maybe.void(Maybe(1)))
|
669 |
+
print(List.void(List([1, 2, 3])))
|
670 |
+
return
|
671 |
+
|
672 |
+
|
673 |
+
@app.cell
|
674 |
+
def _(List, Maybe):
|
675 |
+
print(Maybe.unzip(Maybe(("Hello", "World"))))
|
676 |
+
print(List.unzip(List([("I", "love"), ("really", "λ")])))
|
677 |
+
return
|
678 |
+
|
679 |
|
680 |
+
@app.cell(hide_code=True)
|
681 |
+
def _(mo):
|
682 |
+
mo.md(
|
683 |
+
r"""
|
684 |
+
/// admonition
|
685 |
+
You can always override these utility functions with a more efficient implementation for specific instances.
|
686 |
+
///
|
687 |
"""
|
688 |
)
|
689 |
return
|
|
|
720 |
@classmethod
|
721 |
def void(cls, fa: "Functor[A]") -> "Functor[None]":
|
722 |
return cls.const(fa, None)
|
723 |
+
|
724 |
+
@classmethod
|
725 |
+
def unzip(
|
726 |
+
cls, fab: "Functor[tuple[A, B]]"
|
727 |
+
) -> tuple["Functor[A]", "Functor[B]"]:
|
728 |
+
return cls.fmap(lambda p: p[0], fab), cls.fmap(lambda p: p[1], fab)
|
729 |
return (Functor,)
|
730 |
|
731 |
|
|
|
1247 |
@app.cell(hide_code=True)
|
1248 |
def _():
|
1249 |
from dataclasses import dataclass
|
1250 |
+
from typing import Callable, TypeVar, Union
|
1251 |
from pprint import pp
|
1252 |
+
return Callable, TypeVar, Union, dataclass, pp
|
1253 |
|
1254 |
|
1255 |
@app.cell(hide_code=True)
|
functional_programming/CHANGELOG.md
CHANGED
@@ -6,6 +6,9 @@
|
|
6 |
|
7 |
* restructure the notebook
|
8 |
* replace `f` in the function signatures with `g` to indicate regular functions and distinguish from functors
|
|
|
|
|
|
|
9 |
|
10 |
## 2025-04-02
|
11 |
|
|
|
6 |
|
7 |
* restructure the notebook
|
8 |
* replace `f` in the function signatures with `g` to indicate regular functions and distinguish from functors
|
9 |
+
* move `Maybe` funtor to section `More Functor instances`
|
10 |
+
+ add `Either` functor
|
11 |
+
+ add `unzip` utility function for functors
|
12 |
|
13 |
## 2025-04-02
|
14 |
|