metaboulie commited on
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.3 | last modified: 2025-04-08 | author: [métaboulie](https://github.com/metaboulie)<br/>
44
  reviewer: [Haleshot](https://github.com/Haleshot)
45
 
46
  ///
@@ -293,7 +293,21 @@ def _(mo):
293
  def _(mo):
294
  mo.md(
295
  r"""
296
- ## The Maybe Functor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- ## More Functor instances (optional)
340
 
341
- In this section, we will explore more *Functor* instances to help you build up a better comprehension.
342
 
343
- The main reference is [Data.Functor](https://hackage.haskell.org/package/base-4.21.0.0/docs/Data-Functor.html)
 
 
 
 
344
  """
345
  )
346
  return
347
 
348
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  @app.cell(hide_code=True)
350
  def _(mo):
351
  mo.md(
352
  """
353
- ### The [RoseTree](https://en.wikipedia.org/wiki/Rose_tree) Functor
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 Law Verification
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
- - `const(fa: "Functor[A]", b: B) -> Functor[B]`
580
- Replaces all values inside a functor with a constant `b`, preserving the original structure.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
581
 
582
- - `void(fa: "Functor[A]") -> Functor[None]`
583
- Equivalent to `const(fa, None)`, transforming all values in a functor into `None`.
 
 
 
 
 
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