metaboulie commited on
Commit
7139673
·
1 Parent(s): 8bc43c8

refactor(functor): migrate to python3.13

Browse files
functional_programming/05_functors.py CHANGED
@@ -1,5 +1,5 @@
1
  # /// script
2
- # requires-python = ">=3.9"
3
  # dependencies = [
4
  # "marimo",
5
  # ]
@@ -7,7 +7,7 @@
7
 
8
  import marimo
9
 
10
- __generated_with = "0.11.17"
11
  app = marimo.App(app_title="Category Theory and Functors")
12
 
13
 
@@ -37,7 +37,7 @@ def _(mo):
37
  /// details | Notebook metadata
38
  type: info
39
 
40
- version: 0.1.1 | last modified: 2025-03-16 | author: [métaboulie](https://github.com/metaboulie)<br/>
41
  reviewer: [Haleshot](https://github.com/Haleshot)
42
 
43
  ///
@@ -77,13 +77,13 @@ def _(mo):
77
 
78
  ```python
79
  from dataclasses import dataclass
80
- from typing import Callable, Generic, TypeVar
81
 
82
  A = TypeVar("A")
83
  B = TypeVar("B")
84
 
85
  @dataclass
86
- class Wrapper(Generic[A]):
87
  value: A
88
  ```
89
 
@@ -97,48 +97,38 @@ def _(mo):
97
 
98
  To modify wrapped data while keeping it wrapped, we define an `fmap` method:
99
 
100
- ```python
101
- @dataclass
102
- class Wrapper(Functor, Generic[A]):
103
- value: A
104
-
105
- @classmethod
106
- def fmap(cls, f: Callable[[A], B], a: "Wrapper[A]") -> "Wrapper[B]":
107
- return Wrapper(f(a.value))
108
- ```
109
 
110
- Now, we can apply transformations without unwrapping:
111
 
112
- ```python
113
- >>> Wrapper.fmap(lambda x: x + 1, wrapper)
114
- Wrapper(value=2)
115
-
116
- >>> Wrapper.fmap(lambda x: [x], wrapper)
117
- Wrapper(value=[1])
118
- ```
119
-
120
- > Try using the `Wrapper` in the cell below.
121
  """
122
  )
123
  return
124
 
125
 
126
  @app.cell
127
- def _(A, B, Callable, Functor, Generic, dataclass, pp):
128
  @dataclass
129
- class Wrapper(Functor, Generic[A]):
130
  value: A
131
 
132
  @classmethod
133
- def fmap(cls, f: Callable[[A], B], a: "Wrapper[A]") -> "Wrapper[B]":
134
- return Wrapper(f(a.value))
 
 
 
 
 
 
 
135
 
136
 
 
 
137
  wrapper = Wrapper(1)
138
 
139
  pp(Wrapper.fmap(lambda x: x + 1, wrapper))
140
  pp(Wrapper.fmap(lambda x: [x], wrapper))
141
- return Wrapper, wrapper
142
 
143
 
144
  @app.cell(hide_code=True)
@@ -148,13 +138,13 @@ def _(mo):
148
  We can analyze the type signature of `fmap` for `Wrapper`:
149
 
150
  * `f` is of type `Callable[[A], B]`
151
- * `a` is of type `Wrapper[A]`
152
  * The return value is of type `Wrapper[B]`
153
 
154
  Thus, in Python's type system, we can express the type signature of `fmap` as:
155
 
156
  ```python
157
- fmap(f: Callable[[A], B], a: Wrapper[A]) -> Wrapper[B]:
158
  ```
159
 
160
  Essentially, `fmap`:
@@ -176,46 +166,35 @@ def _(mo):
176
  ## The List Wrapper
177
 
178
  We can define a `List` class to represent a wrapped list that supports `fmap`:
179
-
180
- ```python
181
- @dataclass
182
- class List(Functor, Generic[A]):
183
- value: list[A]
184
-
185
- @classmethod
186
- def fmap(cls, f: Callable[[A], B], a: "List[A]") -> "List[B]":
187
- return List([f(x) for x in a.value])
188
- ```
189
-
190
- Now, we can apply transformations:
191
-
192
- ```python
193
- >>> flist = List([1, 2, 3, 4])
194
- >>> List.fmap(lambda x: x + 1, flist)
195
- List(value=[2, 3, 4, 5])
196
- >>> List.fmap(lambda x: [x], flist)
197
- List(value=[[1], [2], [3], [4]])
198
- ```
199
  """
200
  )
201
  return
202
 
203
 
204
  @app.cell
205
- def _(A, B, Callable, Functor, Generic, dataclass, pp):
206
  @dataclass
207
- class List(Functor, Generic[A]):
208
  value: list[A]
209
 
210
  @classmethod
211
- def fmap(cls, f: Callable[[A], B], a: "List[A]") -> "List[B]":
212
- return List([f(x) for x in a.value])
 
213
 
214
 
 
 
 
 
 
 
 
 
215
  flist = List([1, 2, 3, 4])
216
  pp(List.fmap(lambda x: x + 1, flist))
217
  pp(List.fmap(lambda x: [x], flist))
218
- return List, flist
219
 
220
 
221
  @app.cell(hide_code=True)
@@ -227,19 +206,19 @@ def _(mo):
227
  The type signature of `fmap` for `List` is:
228
 
229
  ```python
230
- fmap(f: Callable[[A], B], a: List[A]) -> List[B]
231
  ```
232
 
233
  Similarly, for `Wrapper`:
234
 
235
  ```python
236
- fmap(f: Callable[[A], B], a: Wrapper[A]) -> Wrapper[B]
237
  ```
238
 
239
  Both follow the same pattern, which we can generalize as:
240
 
241
  ```python
242
- fmap(f: Callable[[A], B], a: Functor[A]) -> Functor[B]
243
  ```
244
 
245
  where `Functor` can be `Wrapper`, `List`, or any other wrapper type that follows the same structure.
@@ -278,17 +257,17 @@ def _(mo):
278
 
279
  ```python
280
  from dataclasses import dataclass
281
- from typing import Callable, Generic, TypeVar
282
  from abc import ABC, abstractmethod
283
 
284
  A = TypeVar("A")
285
  B = TypeVar("B")
286
 
287
  @dataclass
288
- class Functor(ABC, Generic[A]):
289
  @classmethod
290
  @abstractmethod
291
- def fmap(f: Callable[[A], B], a: "Functor[A]") -> "Functor[B]":
292
  raise NotImplementedError
293
  ```
294
 
@@ -321,21 +300,21 @@ def _(mo):
321
 
322
  ```python
323
  from dataclasses import dataclass
324
- from typing import Callable, Generic, TypeVar
325
 
326
  A = TypeVar("A")
327
  B = TypeVar("B")
328
 
329
  @dataclass
330
- class RoseTree(Functor, Generic[a]):
331
-
332
  value: A
333
  children: list["RoseTree[A]"]
334
 
335
  @classmethod
336
- def fmap(cls, f: Callable[[A], B], a: "RoseTree[A]") -> "RoseTree[B]":
337
  return RoseTree(
338
- f(a.value), [cls.fmap(f, child) for child in a.children]
339
  )
340
 
341
  def __repr__(self) -> str:
@@ -353,9 +332,9 @@ def _(mo):
353
 
354
 
355
  @app.cell(hide_code=True)
356
- def _(A, B, Callable, Functor, Generic, dataclass, mo):
357
  @dataclass
358
- class RoseTree(Functor, Generic[A]):
359
  """
360
  ### Doc: RoseTree
361
 
@@ -368,7 +347,7 @@ def _(A, B, Callable, Functor, Generic, dataclass, mo):
368
 
369
  **Methods:**
370
 
371
- - `fmap(f: Callable[[A], B], a: "RoseTree[A]") -> "RoseTree[B]"`
372
 
373
  Applies a function to each value in the tree, producing a new `RoseTree[b]` with transformed values.
374
 
@@ -383,9 +362,9 @@ def _(A, B, Callable, Functor, Generic, dataclass, mo):
383
  children: list["RoseTree[A]"]
384
 
385
  @classmethod
386
- def fmap(cls, f: Callable[[A], B], a: "RoseTree[A]") -> "RoseTree[B]":
387
  return RoseTree(
388
- f(a.value), [cls.fmap(f, child) for child in a.children]
389
  )
390
 
391
  def __repr__(self) -> str:
@@ -549,14 +528,14 @@ def _(mo):
549
 
550
  ```python
551
  @dataclass
552
- class EvilFunctor(Functor, Generic[A]):
553
  value: list[A]
554
 
555
  @classmethod
556
- def fmap(cls, f: Callable[[A], B], a: "EvilFunctor[A]") -> "EvilFunctor[B]":
557
  return (
558
- cls([a.value[0]] * 2 + list(map(f, a.value[1:])))
559
- if a.value
560
  else []
561
  )
562
  ```
@@ -566,18 +545,18 @@ def _(mo):
566
 
567
 
568
  @app.cell
569
- def _(A, B, Callable, Functor, Generic, check_functor_law, dataclass, pp):
570
  @dataclass
571
- class EvilFunctor(Functor, Generic[A]):
572
  value: list[A]
573
 
574
  @classmethod
575
  def fmap(
576
- cls, f: Callable[[A], B], a: "EvilFunctor[A]"
577
  ) -> "EvilFunctor[B]":
578
  return (
579
- cls([a.value[0]] * 2 + [f(x) for x in a.value[1:]])
580
- if a.value
581
  else []
582
  )
583
 
@@ -597,16 +576,16 @@ def _(mo):
597
  ```Python
598
  @classmethod
599
  @abstractmethod
600
- def fmap(cls, f: Callable[[A], B], a: "Functor[A]") -> "Functor[B]":
601
  return NotImplementedError
602
 
603
  @classmethod
604
- def const_fmap(cls, a: "Functor[A]", b: B) -> "Functor[B]":
605
- return cls.fmap(lambda _: b, a)
606
 
607
  @classmethod
608
- def void(cls, a: "Functor[A]") -> "Functor[None]":
609
- return cls.const_fmap(a, None)
610
  ```
611
  """
612
  )
@@ -614,9 +593,9 @@ def _(mo):
614
 
615
 
616
  @app.cell(hide_code=True)
617
- def _(A, ABC, B, Callable, Generic, abstractmethod, dataclass, mo):
618
  @dataclass
619
- class Functor(ABC, Generic[A]):
620
  """
621
  ### Doc: Functor
622
 
@@ -624,28 +603,28 @@ def _(A, ABC, B, Callable, Generic, abstractmethod, dataclass, mo):
624
 
625
  **Methods:**
626
 
627
- - `fmap(f: Callable[[A], B], a: Functor[A]) -> Functor[B]`
628
  Abstract method to apply a function to all values inside a functor.
629
 
630
- - `const_fmap(a: "Functor[A]", b: B) -> Functor[B]`
631
  Replaces all values inside a functor with a constant `b`, preserving the original structure.
632
 
633
- - `void(a: "Functor[A]") -> Functor[None]`
634
- Equivalent to `const_fmap(a, None)`, transforming all values in a functor into `None`.
635
  """
636
 
637
  @classmethod
638
  @abstractmethod
639
- def fmap(cls, f: Callable[[A], B], a: "Functor[A]") -> "Functor[B]":
640
  return NotImplementedError
641
 
642
  @classmethod
643
- def const_fmap(cls, a: "Functor[A]", b: B) -> "Functor[B]":
644
- return cls.fmap(lambda _: b, a)
645
 
646
  @classmethod
647
- def void(cls, a: "Functor[A]") -> "Functor[None]":
648
- return cls.const_fmap(a, None)
649
 
650
 
651
  mo.md(Functor.__doc__)
@@ -682,35 +661,20 @@ def _(mo):
682
  One example is the **`Maybe`** type from Haskell, which is used to represent computations that can either result in a value or no value (`Nothing`).
683
 
684
  We can define the `Maybe` functor as below:
685
-
686
- ```python
687
- @dataclass
688
- class Maybe(Functor, Generic[A]):
689
- value: None | A
690
-
691
- @classmethod
692
- def fmap(cls, f: Callable[[A], B], a: "Maybe[A]") -> "Maybe[B]":
693
- return (
694
- cls(None) if a.value is None else cls(f(a.value))
695
- )
696
-
697
- def __repr__(self):
698
- return "Nothing" if self.value is None else repr(self.value)
699
- ```
700
  """
701
  )
702
  return
703
 
704
 
705
  @app.cell
706
- def _(A, B, Callable, Functor, Generic, dataclass):
707
  @dataclass
708
- class Maybe(Functor, Generic[A]):
709
  value: None | A
710
 
711
  @classmethod
712
- def fmap(cls, f: Callable[[A], B], a: "Maybe[A]") -> "Maybe[B]":
713
- return cls(None) if a.value is None else cls(f(a.value))
714
 
715
  def __repr__(self):
716
  return "Nothing" if self.value is None else repr(self.value)
@@ -815,7 +779,7 @@ def _(mo):
815
  Remember that we defined the `id` and `compose` function above as:
816
 
817
  ```Python
818
- def id(x: Generic[A]) -> Generic[A]:
819
  return x
820
 
821
  def compose(f: Callable[[B], C], g: Callable[[A], B]) -> Callable[[A], C]:
@@ -893,14 +857,14 @@ def _(mo):
893
 
894
  ```Python
895
  @dataclass
896
- class Functor(ABC, Generic[A])
897
  ```
898
 
899
  And RoseTree:
900
 
901
  ```Python
902
  @dataclass
903
- class RoseTree(Functor, Generic[A])
904
  ```
905
 
906
  **Here's the key part:** the _type constructor_ `RoseTree` takes any type `T` to a new type, `RoseTree[T]`. Also, `fmap` restricted to `RoseTree` types takes a function `Callable[[A], B]` to a function `Callable[[RoseTree[A]], RoseTree[B]]`.
@@ -977,7 +941,7 @@ def _(mo):
977
 
978
  ```Python
979
  @dataclass
980
- class Wrapper(Functor, Generic[A]):
981
  value: A
982
 
983
  @classmethod
@@ -1063,31 +1027,15 @@ def _(mo):
1063
  ### Category of List Concatenation
1064
 
1065
  First, let’s define the category of list concatenation:
1066
-
1067
- ```python
1068
- @dataclass
1069
- class ListConcatenation(Generic[A]):
1070
- value: list[A]
1071
-
1072
- @staticmethod
1073
- def id() -> "ListConcatenation[A]":
1074
- return ListConcatenation([])
1075
-
1076
- @staticmethod
1077
- def compose(
1078
- this: "ListConcatenation[A]", other: "ListConcatenation[A]"
1079
- ) -> "ListConcatenation[a]":
1080
- return ListConcatenation(this.value + other.value)
1081
- ```
1082
  """
1083
  )
1084
  return
1085
 
1086
 
1087
  @app.cell
1088
- def _(A, Generic, dataclass):
1089
  @dataclass
1090
- class ListConcatenation(Generic[A]):
1091
  value: list[A]
1092
 
1093
  @staticmethod
@@ -1120,20 +1068,6 @@ def _(mo):
1120
  ### Category of Integer Addition
1121
 
1122
  Now, let's define the category of integer addition:
1123
-
1124
- ```python
1125
- @dataclass
1126
- class IntAddition:
1127
- value: int
1128
-
1129
- @staticmethod
1130
- def id() -> "IntAddition":
1131
- return IntAddition(0)
1132
-
1133
- @staticmethod
1134
- def compose(this: "IntAddition", other: "IntAddition") -> "IntAddition":
1135
- return IntAddition(this.value + other.value)
1136
- ```
1137
  """
1138
  )
1139
  return
@@ -1296,9 +1230,9 @@ def _():
1296
  @app.cell(hide_code=True)
1297
  def _():
1298
  from dataclasses import dataclass
1299
- from typing import Callable, Generic, TypeVar
1300
  from pprint import pp
1301
- return Callable, Generic, TypeVar, dataclass, pp
1302
 
1303
 
1304
  @app.cell(hide_code=True)
 
1
  # /// script
2
+ # requires-python = ">=3.13"
3
  # dependencies = [
4
  # "marimo",
5
  # ]
 
7
 
8
  import marimo
9
 
10
+ __generated_with = "0.12.0"
11
  app = marimo.App(app_title="Category Theory and Functors")
12
 
13
 
 
37
  /// details | Notebook metadata
38
  type: info
39
 
40
+ version: 0.1.2 | last modified: 2025-04-02 | author: [métaboulie](https://github.com/metaboulie)<br/>
41
  reviewer: [Haleshot](https://github.com/Haleshot)
42
 
43
  ///
 
77
 
78
  ```python
79
  from dataclasses import dataclass
80
+ from typing import Callable, TypeVar
81
 
82
  A = TypeVar("A")
83
  B = TypeVar("B")
84
 
85
  @dataclass
86
+ class Wrapper[A]:
87
  value: A
88
  ```
89
 
 
97
 
98
  To modify wrapped data while keeping it wrapped, we define an `fmap` method:
99
 
 
 
 
 
 
 
 
 
 
100
 
 
101
 
 
 
 
 
 
 
 
 
 
102
  """
103
  )
104
  return
105
 
106
 
107
  @app.cell
108
+ def _(B, Callable, Functor, dataclass):
109
  @dataclass
110
+ class Wrapper[A](Functor):
111
  value: A
112
 
113
  @classmethod
114
+ def fmap(cls, f: Callable[[A], B], fa: "Wrapper[A]") -> "Wrapper[B]":
115
+ return Wrapper(f(fa.value))
116
+ return (Wrapper,)
117
+
118
+
119
+ @app.cell(hide_code=True)
120
+ def _(mo):
121
+ mo.md(r"""> Try with Wrapper below""")
122
+ return
123
 
124
 
125
+ @app.cell
126
+ def _(Wrapper, pp):
127
  wrapper = Wrapper(1)
128
 
129
  pp(Wrapper.fmap(lambda x: x + 1, wrapper))
130
  pp(Wrapper.fmap(lambda x: [x], wrapper))
131
+ return (wrapper,)
132
 
133
 
134
  @app.cell(hide_code=True)
 
138
  We can analyze the type signature of `fmap` for `Wrapper`:
139
 
140
  * `f` is of type `Callable[[A], B]`
141
+ * `fa` is of type `Wrapper[A]`
142
  * The return value is of type `Wrapper[B]`
143
 
144
  Thus, in Python's type system, we can express the type signature of `fmap` as:
145
 
146
  ```python
147
+ fmap(f: Callable[[A], B], fa: Wrapper[A]) -> Wrapper[B]:
148
  ```
149
 
150
  Essentially, `fmap`:
 
166
  ## The List Wrapper
167
 
168
  We can define a `List` class to represent a wrapped list that supports `fmap`:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  """
170
  )
171
  return
172
 
173
 
174
  @app.cell
175
+ def _(B, Callable, Functor, dataclass):
176
  @dataclass
177
+ class List[A](Functor):
178
  value: list[A]
179
 
180
  @classmethod
181
+ def fmap(cls, f: Callable[[A], B], fa: "List[A]") -> "List[B]":
182
+ return List([f(x) for x in fa.value])
183
+ return (List,)
184
 
185
 
186
+ @app.cell(hide_code=True)
187
+ def _(mo):
188
+ mo.md(r"""> Try with List below""")
189
+ return
190
+
191
+
192
+ @app.cell
193
+ def _(List, pp):
194
  flist = List([1, 2, 3, 4])
195
  pp(List.fmap(lambda x: x + 1, flist))
196
  pp(List.fmap(lambda x: [x], flist))
197
+ return (flist,)
198
 
199
 
200
  @app.cell(hide_code=True)
 
206
  The type signature of `fmap` for `List` is:
207
 
208
  ```python
209
+ fmap(f: Callable[[A], B], fa: List[A]) -> List[B]
210
  ```
211
 
212
  Similarly, for `Wrapper`:
213
 
214
  ```python
215
+ fmap(f: Callable[[A], B], fa: Wrapper[A]) -> Wrapper[B]
216
  ```
217
 
218
  Both follow the same pattern, which we can generalize as:
219
 
220
  ```python
221
+ fmap(f: Callable[[A], B], fa: Functor[A]) -> Functor[B]
222
  ```
223
 
224
  where `Functor` can be `Wrapper`, `List`, or any other wrapper type that follows the same structure.
 
257
 
258
  ```python
259
  from dataclasses import dataclass
260
+ from typing import Callable, TypeVar
261
  from abc import ABC, abstractmethod
262
 
263
  A = TypeVar("A")
264
  B = TypeVar("B")
265
 
266
  @dataclass
267
+ class Functor[A](ABC):
268
  @classmethod
269
  @abstractmethod
270
+ def fmap(f: Callable[[A], B], fa: "Functor[A]") -> "Functor[B]":
271
  raise NotImplementedError
272
  ```
273
 
 
300
 
301
  ```python
302
  from dataclasses import dataclass
303
+ from typing import Callable, TypeVar
304
 
305
  A = TypeVar("A")
306
  B = TypeVar("B")
307
 
308
  @dataclass
309
+ class RoseTree[A](Functor):
310
+
311
  value: A
312
  children: list["RoseTree[A]"]
313
 
314
  @classmethod
315
+ def fmap(cls, f: Callable[[A], B], fa: "RoseTree[A]") -> "RoseTree[B]":
316
  return RoseTree(
317
+ f(fa.value), [cls.fmap(f, child) for child in fa.children]
318
  )
319
 
320
  def __repr__(self) -> str:
 
332
 
333
 
334
  @app.cell(hide_code=True)
335
+ def _(B, Callable, Functor, dataclass, mo):
336
  @dataclass
337
+ class RoseTree[A](Functor):
338
  """
339
  ### Doc: RoseTree
340
 
 
347
 
348
  **Methods:**
349
 
350
+ - `fmap(f: Callable[[A], B], fa: "RoseTree[A]") -> "RoseTree[B]"`
351
 
352
  Applies a function to each value in the tree, producing a new `RoseTree[b]` with transformed values.
353
 
 
362
  children: list["RoseTree[A]"]
363
 
364
  @classmethod
365
+ def fmap(cls, f: Callable[[A], B], fa: "RoseTree[A]") -> "RoseTree[B]":
366
  return RoseTree(
367
+ f(fa.value), [cls.fmap(f, child) for child in fa.children]
368
  )
369
 
370
  def __repr__(self) -> str:
 
528
 
529
  ```python
530
  @dataclass
531
+ class EvilFunctor[A](Functor):
532
  value: list[A]
533
 
534
  @classmethod
535
+ def fmap(cls, f: Callable[[A], B], fa: "EvilFunctor[A]") -> "EvilFunctor[B]":
536
  return (
537
+ cls([fa.value[0]] * 2 + list(map(f, fa.value[1:])))
538
+ if fa.value
539
  else []
540
  )
541
  ```
 
545
 
546
 
547
  @app.cell
548
+ def _(B, Callable, Functor, check_functor_law, dataclass, pp):
549
  @dataclass
550
+ class EvilFunctor[A](Functor):
551
  value: list[A]
552
 
553
  @classmethod
554
  def fmap(
555
+ cls, f: Callable[[A], B], fa: "EvilFunctor[A]"
556
  ) -> "EvilFunctor[B]":
557
  return (
558
+ cls([fa.value[0]] * 2 + [f(x) for x in fa.value[1:]])
559
+ if fa.value
560
  else []
561
  )
562
 
 
576
  ```Python
577
  @classmethod
578
  @abstractmethod
579
+ def fmap(cls, f: Callable[[A], B], fa: "Functor[A]") -> "Functor[B]":
580
  return NotImplementedError
581
 
582
  @classmethod
583
+ def const_fmap(cls, fa: "Functor[A]", b: B) -> "Functor[B]":
584
+ return cls.fmap(lambda _: b, fa)
585
 
586
  @classmethod
587
+ def void(cls, fa: "Functor[A]") -> "Functor[None]":
588
+ return cls.const_fmap(fa, None)
589
  ```
590
  """
591
  )
 
593
 
594
 
595
  @app.cell(hide_code=True)
596
+ def _(ABC, B, Callable, abstractmethod, dataclass, mo):
597
  @dataclass
598
+ class Functor[A](ABC):
599
  """
600
  ### Doc: Functor
601
 
 
603
 
604
  **Methods:**
605
 
606
+ - `fmap(f: Callable[[A], B], fa: Functor[A]) -> Functor[B]`
607
  Abstract method to apply a function to all values inside a functor.
608
 
609
+ - `const_fmap(fa: "Functor[A]", b: B) -> Functor[B]`
610
  Replaces all values inside a functor with a constant `b`, preserving the original structure.
611
 
612
+ - `void(fa: "Functor[A]") -> Functor[None]`
613
+ Equivalent to `const_fmap(fa, None)`, transforming all values in a functor into `None`.
614
  """
615
 
616
  @classmethod
617
  @abstractmethod
618
+ def fmap(cls, f: Callable[[A], B], fa: "Functor[A]") -> "Functor[B]":
619
  return NotImplementedError
620
 
621
  @classmethod
622
+ def const_fmap(cls, fa: "Functor[A]", b: B) -> "Functor[B]":
623
+ return cls.fmap(lambda _: b, fa)
624
 
625
  @classmethod
626
+ def void(cls, fa: "Functor[A]") -> "Functor[None]":
627
+ return cls.const_fmap(fa, None)
628
 
629
 
630
  mo.md(Functor.__doc__)
 
661
  One example is the **`Maybe`** type from Haskell, which is used to represent computations that can either result in a value or no value (`Nothing`).
662
 
663
  We can define the `Maybe` functor as below:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
  """
665
  )
666
  return
667
 
668
 
669
  @app.cell
670
+ def _(B, Callable, Functor, dataclass):
671
  @dataclass
672
+ class Maybe[A](Functor):
673
  value: None | A
674
 
675
  @classmethod
676
+ def fmap(cls, f: Callable[[A], B], fa: "Maybe[A]") -> "Maybe[B]":
677
+ return cls(None) if fa.value is None else cls(f(fa.value))
678
 
679
  def __repr__(self):
680
  return "Nothing" if self.value is None else repr(self.value)
 
779
  Remember that we defined the `id` and `compose` function above as:
780
 
781
  ```Python
782
+ def id(x: A) -> A:
783
  return x
784
 
785
  def compose(f: Callable[[B], C], g: Callable[[A], B]) -> Callable[[A], C]:
 
857
 
858
  ```Python
859
  @dataclass
860
+ class Functor[A](ABC)
861
  ```
862
 
863
  And RoseTree:
864
 
865
  ```Python
866
  @dataclass
867
+ class RoseTree[A](Functor)
868
  ```
869
 
870
  **Here's the key part:** the _type constructor_ `RoseTree` takes any type `T` to a new type, `RoseTree[T]`. Also, `fmap` restricted to `RoseTree` types takes a function `Callable[[A], B]` to a function `Callable[[RoseTree[A]], RoseTree[B]]`.
 
941
 
942
  ```Python
943
  @dataclass
944
+ class Wrapper[A](Functor):
945
  value: A
946
 
947
  @classmethod
 
1027
  ### Category of List Concatenation
1028
 
1029
  First, let’s define the category of list concatenation:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1030
  """
1031
  )
1032
  return
1033
 
1034
 
1035
  @app.cell
1036
+ def _(A, dataclass):
1037
  @dataclass
1038
+ class ListConcatenation[A]:
1039
  value: list[A]
1040
 
1041
  @staticmethod
 
1068
  ### Category of Integer Addition
1069
 
1070
  Now, let's define the category of integer addition:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1071
  """
1072
  )
1073
  return
 
1230
  @app.cell(hide_code=True)
1231
  def _():
1232
  from dataclasses import dataclass
1233
+ from typing import Callable, TypeVar
1234
  from pprint import pp
1235
+ return Callable, TypeVar, dataclass, pp
1236
 
1237
 
1238
  @app.cell(hide_code=True)
functional_programming/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
  # Changelog of the functional-programming course
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  ## 2025-03-11
4
 
5
  * Demo version of notebook `05_functors.py`
 
1
  # Changelog of the functional-programming course
2
 
3
+ ## 2025-04-02
4
+
5
+ + Migrate to `python3.13` for `05_functors`
6
+
7
+ + Replace all occurrences of
8
+
9
+ ```python
10
+ class Functor(Generic[A])
11
+ ```
12
+
13
+ with
14
+
15
+ ```python
16
+ class Functor[A]
17
+ ```
18
+
19
+ for conciseness
20
+
21
+ + Use `fa` in function signatures instead of `a` when `fa` is a *Functor*
22
+
23
  ## 2025-03-11
24
 
25
  * Demo version of notebook `05_functors.py`