Spaces:
Running
Running
Commit
·
e485eac
1
Parent(s):
48feff6
refactor(applicatives): `v0.1.3` of `applicatives.py`
Browse files
functional_programming/06_applicatives.py
CHANGED
@@ -29,12 +29,12 @@ def _(mo):
|
|
29 |
1. How to view `applicative` as multi-functor.
|
30 |
2. How to use `lift` to simplify chaining application.
|
31 |
3. How to bring *effects* to the functional pure world.
|
32 |
-
4. How to view `applicative` as lax monoidal functor
|
33 |
|
34 |
/// details | Notebook metadata
|
35 |
type: info
|
36 |
|
37 |
-
version: 0.1.
|
38 |
|
39 |
///
|
40 |
"""
|
@@ -76,12 +76,15 @@ def _(mo):
|
|
76 |
r"""
|
77 |
## Defining Multifunctor
|
78 |
|
|
|
|
|
|
|
|
|
79 |
As a result, we may want to define a single `Multifunctor` such that:
|
80 |
|
81 |
1. Lift a regular n-argument function into the context of functors
|
82 |
|
83 |
```python
|
84 |
-
# we use prefix `f` here to indicate `Functor`
|
85 |
# lift a regular 3-argument function `g`
|
86 |
g: Callable[[A, B, C], D]
|
87 |
# into the context of functors
|
@@ -119,7 +122,7 @@ def _(mo):
|
|
119 |
|
120 |
Traditionally, applicative functors are presented through two core operations:
|
121 |
|
122 |
-
1. `pure`: embeds an object (value or function) into the functor
|
123 |
|
124 |
```python
|
125 |
# a -> F a
|
@@ -134,7 +137,7 @@ def _(mo):
|
|
134 |
fg: Applicative[Callable[[A], B]] = pure(g)
|
135 |
```
|
136 |
|
137 |
-
2. `apply`: applies a function inside
|
138 |
|
139 |
```python
|
140 |
# F (a -> b) -> F a -> F b
|
@@ -174,9 +177,9 @@ def _(mo):
|
|
174 |
/// attention | You can suppress the chaining application of `apply` and `pure` as:
|
175 |
|
176 |
```python
|
177 |
-
apply(pure(
|
178 |
-
apply(apply(pure(
|
179 |
-
apply(apply(apply(pure(
|
180 |
```
|
181 |
|
182 |
///
|
@@ -240,7 +243,7 @@ def _(mo):
|
|
240 |
r"""
|
241 |
## Applicative instances
|
242 |
|
243 |
-
When we are actually implementing an *Applicative* instance, we can keep in mind that `pure` and `apply`
|
244 |
|
245 |
- embed an object (value or function) to the computational context
|
246 |
- apply a function inside the computation context to a value inside the computational context
|
@@ -261,7 +264,7 @@ def _(mo):
|
|
261 |
Wrapper.pure(1) => Wrapper(value=1)
|
262 |
```
|
263 |
|
264 |
-
- `apply` should apply a *wrapped* function to a *
|
265 |
|
266 |
The implementation is:
|
267 |
"""
|
@@ -376,7 +379,7 @@ def _(mo):
|
|
376 |
```
|
377 |
|
378 |
- `apply` should apply a function maybe exist to a value maybe exist
|
379 |
-
- if the function is `None`,
|
380 |
- else apply the function to the value and wrap the result in `Just`
|
381 |
|
382 |
The implementation is:
|
@@ -399,12 +402,13 @@ def _(Applicative, dataclass):
|
|
399 |
def apply(
|
400 |
cls, fg: "Maybe[Callable[[A], B]]", fa: "Maybe[A]"
|
401 |
) -> "Maybe[B]":
|
402 |
-
if fg.value is None:
|
403 |
return cls(None)
|
|
|
404 |
return cls(fg.value(fa.value))
|
405 |
|
406 |
def __repr__(self):
|
407 |
-
return "Nothing" if self.value is None else
|
408 |
return (Maybe,)
|
409 |
|
410 |
|
@@ -434,12 +438,87 @@ def _(Maybe):
|
|
434 |
return
|
435 |
|
436 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
437 |
@app.cell(hide_code=True)
|
438 |
def _(mo):
|
439 |
mo.md(
|
440 |
r"""
|
441 |
## Applicative laws
|
442 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
443 |
Traditionally, there are four laws that `Applicative` instances should satisfy. In some sense, they are all concerned with making sure that `pure` deserves its name:
|
444 |
|
445 |
- The identity law:
|
@@ -468,16 +547,7 @@ def _(mo):
|
|
468 |
# fa: Applicative[A]
|
469 |
apply(fg, apply(fh, fa)) = lift(compose, fg, fh, fa)
|
470 |
```
|
471 |
-
This one is the trickiest law to gain intuition for. In some sense it is expressing a sort of associativity property of
|
472 |
-
|
473 |
-
/// admonition | id and compose
|
474 |
-
|
475 |
-
Remember that
|
476 |
-
|
477 |
-
- id = lambda x: x
|
478 |
-
- compose = lambda f: lambda g: lambda x: f(g(x))
|
479 |
-
|
480 |
-
///
|
481 |
|
482 |
We can add 4 helper functions to `Applicative` to check whether an instance respects the laws or not:
|
483 |
|
@@ -561,7 +631,7 @@ def _(mo):
|
|
561 |
r"""
|
562 |
## Utility functions
|
563 |
|
564 |
-
/// attention
|
565 |
`fmap` is defined automatically using `pure` and `apply`, so you can use `fmap` with any `Applicative`
|
566 |
///
|
567 |
|
@@ -599,24 +669,14 @@ def _(mo):
|
|
599 |
return cls.lift(lambda a: lambda f: f(a), fa, fg)
|
600 |
```
|
601 |
|
602 |
-
|
|
|
|
|
603 |
"""
|
604 |
)
|
605 |
return
|
606 |
|
607 |
|
608 |
-
@app.cell
|
609 |
-
def _(Maybe):
|
610 |
-
print("Maybe.skip")
|
611 |
-
print(Maybe.skip(Maybe(1), Maybe(None)))
|
612 |
-
print(Maybe.skip(Maybe(None), Maybe(1)))
|
613 |
-
|
614 |
-
print("\nMaybe.keep")
|
615 |
-
print(Maybe.keep(Maybe(1), Maybe(None)))
|
616 |
-
print(Maybe.keep(Maybe(None), Maybe(1)))
|
617 |
-
return
|
618 |
-
|
619 |
-
|
620 |
@app.cell(hide_code=True)
|
621 |
def _(mo):
|
622 |
mo.md(
|
@@ -679,6 +739,7 @@ def _(
|
|
679 |
|
680 |
for arg in args:
|
681 |
curr = cls.apply(curr, arg)
|
|
|
682 |
return curr
|
683 |
|
684 |
@classmethod
|
@@ -687,6 +748,19 @@ def _(
|
|
687 |
) -> "Applicative[B]":
|
688 |
return cls.lift(f, fa)
|
689 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
690 |
@classmethod
|
691 |
def skip(
|
692 |
cls, fa: "Applicative[A]", fb: "Applicative[B]"
|
@@ -754,9 +828,9 @@ def _(mo):
|
|
754 |
r"""
|
755 |
# Effectful programming
|
756 |
|
757 |
-
Our original motivation for applicatives was the desire
|
758 |
|
759 |
-
the arguments are no longer just plain values but may also have effects, such as the possibility of failure, having many ways to succeed, or performing input/output actions. In this manner, applicative functors can also be viewed as abstracting the idea of applying pure functions to effectful arguments
|
760 |
"""
|
761 |
)
|
762 |
return
|
@@ -779,7 +853,7 @@ def _(mo):
|
|
779 |
IO.pure(f) => IO(effect=f)
|
780 |
```
|
781 |
|
782 |
-
- `apply` should perform an action that produces a
|
783 |
|
784 |
The implementation is:
|
785 |
"""
|
@@ -798,13 +872,11 @@ def _(Applicative, Callable, dataclass):
|
|
798 |
|
799 |
@classmethod
|
800 |
def pure(cls, a):
|
801 |
-
"""Lift a value into the IO context"""
|
802 |
return cls(a) if isinstance(a, Callable) else IO(lambda: a)
|
803 |
|
804 |
@classmethod
|
805 |
-
def apply(cls,
|
806 |
-
|
807 |
-
return cls.pure(f()(a()))
|
808 |
return (IO,)
|
809 |
|
810 |
|
@@ -817,19 +889,15 @@ def _(mo):
|
|
817 |
@app.cell
|
818 |
def _(IO):
|
819 |
def get_chars(n: int = 3):
|
820 |
-
|
821 |
-
|
822 |
-
return IO.lift(
|
823 |
-
lambda: lambda s1: lambda: lambda s2: s1 + "\n" + s2,
|
824 |
-
IO.pure(input("input line")),
|
825 |
-
IO.pure(get_chars(n - 1)),
|
826 |
)
|
827 |
return (get_chars,)
|
828 |
|
829 |
|
830 |
@app.cell
|
831 |
def _():
|
832 |
-
#
|
833 |
return
|
834 |
|
835 |
|
@@ -932,12 +1000,12 @@ def _(mo):
|
|
932 |
|
933 |
```python
|
934 |
pure(a) = fmap((lambda _: a), unit)
|
935 |
-
apply(
|
936 |
```
|
937 |
|
938 |
```python
|
939 |
unit() = pure(())
|
940 |
-
tensor(fa, fb) = lift( ,fa, fb)
|
941 |
```
|
942 |
"""
|
943 |
)
|
@@ -985,15 +1053,12 @@ def _(B, Callable, Monoidal, dataclass, product):
|
|
985 |
cls, f: Callable[[A], B], ma: "ListMonoidal[A]"
|
986 |
) -> "ListMonoidal[B]":
|
987 |
return cls([f(a) for a in ma.items])
|
988 |
-
|
989 |
-
def __repr__(self):
|
990 |
-
return repr(self.items)
|
991 |
return (ListMonoidal,)
|
992 |
|
993 |
|
994 |
@app.cell(hide_code=True)
|
995 |
def _(mo):
|
996 |
-
mo.md(r"""> try with
|
997 |
return
|
998 |
|
999 |
|
@@ -1005,6 +1070,18 @@ def _(ListMonoidal):
|
|
1005 |
return xs, ys
|
1006 |
|
1007 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1008 |
@app.cell(hide_code=True)
|
1009 |
def _(ABC, B, Callable, abstractmethod, dataclass):
|
1010 |
@dataclass
|
|
|
29 |
1. How to view `applicative` as multi-functor.
|
30 |
2. How to use `lift` to simplify chaining application.
|
31 |
3. How to bring *effects* to the functional pure world.
|
32 |
+
4. How to view `applicative` as lax monoidal functor.
|
33 |
|
34 |
/// details | Notebook metadata
|
35 |
type: info
|
36 |
|
37 |
+
version: 0.1.2 | last modified: 2025-04-07 | author: [métaboulie](https://github.com/metaboulie)<br/>
|
38 |
|
39 |
///
|
40 |
"""
|
|
|
76 |
r"""
|
77 |
## Defining Multifunctor
|
78 |
|
79 |
+
/// admonition
|
80 |
+
we use prefix `f` rather than `ap` to indicate *Applicative Functor*
|
81 |
+
///
|
82 |
+
|
83 |
As a result, we may want to define a single `Multifunctor` such that:
|
84 |
|
85 |
1. Lift a regular n-argument function into the context of functors
|
86 |
|
87 |
```python
|
|
|
88 |
# lift a regular 3-argument function `g`
|
89 |
g: Callable[[A, B, C], D]
|
90 |
# into the context of functors
|
|
|
122 |
|
123 |
Traditionally, applicative functors are presented through two core operations:
|
124 |
|
125 |
+
1. `pure`: embeds an object (value or function) into the applicative functor
|
126 |
|
127 |
```python
|
128 |
# a -> F a
|
|
|
137 |
fg: Applicative[Callable[[A], B]] = pure(g)
|
138 |
```
|
139 |
|
140 |
+
2. `apply`: applies a function inside an applicative functor to a value inside an applicative functor
|
141 |
|
142 |
```python
|
143 |
# F (a -> b) -> F a -> F b
|
|
|
177 |
/// attention | You can suppress the chaining application of `apply` and `pure` as:
|
178 |
|
179 |
```python
|
180 |
+
apply(pure(g), fa) -> lift(g, fa)
|
181 |
+
apply(apply(pure(g), fa), fb) -> lift(g, fa, fb)
|
182 |
+
apply(apply(apply(pure(g), fa), fb), fc) -> lift(g, fa, fb, fc)
|
183 |
```
|
184 |
|
185 |
///
|
|
|
243 |
r"""
|
244 |
## Applicative instances
|
245 |
|
246 |
+
When we are actually implementing an *Applicative* instance, we can keep in mind that `pure` and `apply` fundamentally:
|
247 |
|
248 |
- embed an object (value or function) to the computational context
|
249 |
- apply a function inside the computation context to a value inside the computational context
|
|
|
264 |
Wrapper.pure(1) => Wrapper(value=1)
|
265 |
```
|
266 |
|
267 |
+
- `apply` should apply a *wrapped* function to a *wrapped* value
|
268 |
|
269 |
The implementation is:
|
270 |
"""
|
|
|
379 |
```
|
380 |
|
381 |
- `apply` should apply a function maybe exist to a value maybe exist
|
382 |
+
- if the function is `None` or the value is `None`, simply returns `None`
|
383 |
- else apply the function to the value and wrap the result in `Just`
|
384 |
|
385 |
The implementation is:
|
|
|
402 |
def apply(
|
403 |
cls, fg: "Maybe[Callable[[A], B]]", fa: "Maybe[A]"
|
404 |
) -> "Maybe[B]":
|
405 |
+
if fg.value is None or fa.value is None:
|
406 |
return cls(None)
|
407 |
+
|
408 |
return cls(fg.value(fa.value))
|
409 |
|
410 |
def __repr__(self):
|
411 |
+
return "Nothing" if self.value is None else f"Just({self.value!r})"
|
412 |
return (Maybe,)
|
413 |
|
414 |
|
|
|
438 |
return
|
439 |
|
440 |
|
441 |
+
@app.cell(hide_code=True)
|
442 |
+
def _(mo):
|
443 |
+
mo.md(
|
444 |
+
r"""
|
445 |
+
## Collect the list of response with sequenceL
|
446 |
+
|
447 |
+
One often wants to execute a list of commands and collect the list of their response, and we can define a function `sequenceL` for this
|
448 |
+
|
449 |
+
/// admonition
|
450 |
+
In a further notebook about `Traversable`, we will have a more generic `sequence` that execute a **sequence** of commands and collect the **sequence** of their response, which is not limited to `list`.
|
451 |
+
///
|
452 |
+
|
453 |
+
```python
|
454 |
+
@classmethod
|
455 |
+
def sequenceL(cls, fas: list["Applicative[A]"]) -> "Applicative[list[A]]":
|
456 |
+
if not fas:
|
457 |
+
return cls.pure([])
|
458 |
+
|
459 |
+
return cls.apply(
|
460 |
+
cls.fmap(lambda v: lambda vs: [v] + vs, fas[0]),
|
461 |
+
cls.sequenceL(fas[1:]),
|
462 |
+
)
|
463 |
+
```
|
464 |
+
|
465 |
+
Let's try `sequenceL` with the instances.
|
466 |
+
"""
|
467 |
+
)
|
468 |
+
return
|
469 |
+
|
470 |
+
|
471 |
+
@app.cell
|
472 |
+
def _(Wrapper):
|
473 |
+
Wrapper.sequenceL([Wrapper(1), Wrapper(2), Wrapper(3)])
|
474 |
+
return
|
475 |
+
|
476 |
+
|
477 |
+
@app.cell(hide_code=True)
|
478 |
+
def _(mo):
|
479 |
+
mo.md(
|
480 |
+
r"""
|
481 |
+
/// attention
|
482 |
+
For the `Maybe` Applicative, the presence of any `Nothing` causes the entire computation to return Nothing.
|
483 |
+
///
|
484 |
+
"""
|
485 |
+
)
|
486 |
+
return
|
487 |
+
|
488 |
+
|
489 |
+
@app.cell
|
490 |
+
def _(Maybe):
|
491 |
+
Maybe.sequenceL([Maybe(1), Maybe(2), Maybe(None), Maybe(3)])
|
492 |
+
return
|
493 |
+
|
494 |
+
|
495 |
+
@app.cell(hide_code=True)
|
496 |
+
def _(mo):
|
497 |
+
mo.md(r"""The result of `sequenceL` for `List Applicative` is the Cartesian product of the input lists, yielding all possible ordered combinations of elements from each list.""")
|
498 |
+
return
|
499 |
+
|
500 |
+
|
501 |
+
@app.cell
|
502 |
+
def _(List):
|
503 |
+
List.sequenceL([List([1, 2]), List([3]), List([5, 6, 7])])
|
504 |
+
return
|
505 |
+
|
506 |
+
|
507 |
@app.cell(hide_code=True)
|
508 |
def _(mo):
|
509 |
mo.md(
|
510 |
r"""
|
511 |
## Applicative laws
|
512 |
|
513 |
+
/// admonition | id and compose
|
514 |
+
|
515 |
+
Remember that
|
516 |
+
|
517 |
+
- `id = lambda x: x`
|
518 |
+
- `compose = lambda f: lambda g: lambda x: f(g(x))`
|
519 |
+
|
520 |
+
///
|
521 |
+
|
522 |
Traditionally, there are four laws that `Applicative` instances should satisfy. In some sense, they are all concerned with making sure that `pure` deserves its name:
|
523 |
|
524 |
- The identity law:
|
|
|
547 |
# fa: Applicative[A]
|
548 |
apply(fg, apply(fh, fa)) = lift(compose, fg, fh, fa)
|
549 |
```
|
550 |
+
This one is the trickiest law to gain intuition for. In some sense it is expressing a sort of associativity property of `apply`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
551 |
|
552 |
We can add 4 helper functions to `Applicative` to check whether an instance respects the laws or not:
|
553 |
|
|
|
631 |
r"""
|
632 |
## Utility functions
|
633 |
|
634 |
+
/// attention | using `fmap`
|
635 |
`fmap` is defined automatically using `pure` and `apply`, so you can use `fmap` with any `Applicative`
|
636 |
///
|
637 |
|
|
|
669 |
return cls.lift(lambda a: lambda f: f(a), fa, fg)
|
670 |
```
|
671 |
|
672 |
+
- `skip` sequences the effects of two Applicative computations, but **discards the result of the first**. For example, if `m1` and `m2` are instances of type `Maybe[Int]`, then `Maybe.skip(m1, m2)` is `Nothing` whenever either `m1` or `m2` is `Nothing`; but if not, it will have the same value as `m2`.
|
673 |
+
- Likewise, `keep` sequences the effects of two computations, but **keeps only the result of the first**.
|
674 |
+
- `revapp` is similar to `apply`, but where the first computation produces value(s) which are provided as input to the function(s) produced by the second computation.
|
675 |
"""
|
676 |
)
|
677 |
return
|
678 |
|
679 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
680 |
@app.cell(hide_code=True)
|
681 |
def _(mo):
|
682 |
mo.md(
|
|
|
739 |
|
740 |
for arg in args:
|
741 |
curr = cls.apply(curr, arg)
|
742 |
+
|
743 |
return curr
|
744 |
|
745 |
@classmethod
|
|
|
748 |
) -> "Applicative[B]":
|
749 |
return cls.lift(f, fa)
|
750 |
|
751 |
+
@classmethod
|
752 |
+
def sequenceL(cls, fas: list["Applicative[A]"]) -> "Applicative[list[A]]":
|
753 |
+
"""
|
754 |
+
Execute a list of commands and collect the list of their response.
|
755 |
+
"""
|
756 |
+
if not fas:
|
757 |
+
return cls.pure([])
|
758 |
+
|
759 |
+
return cls.apply(
|
760 |
+
cls.fmap(lambda v: lambda vs: [v] + vs, fas[0]),
|
761 |
+
cls.sequenceL(fas[1:]),
|
762 |
+
)
|
763 |
+
|
764 |
@classmethod
|
765 |
def skip(
|
766 |
cls, fa: "Applicative[A]", fb: "Applicative[B]"
|
|
|
828 |
r"""
|
829 |
# Effectful programming
|
830 |
|
831 |
+
Our original motivation for applicatives was the desire to generalise the idea of mapping to functions with multiple arguments. This is a valid interpretation of the concept of applicatives, but from the three instances we have seen it becomes clear that there is also another, more abstract view.
|
832 |
|
833 |
+
the arguments are no longer just plain values but may also have effects, such as the possibility of failure, having many ways to succeed, or performing input/output actions. In this manner, applicative functors can also be viewed as abstracting the idea of **applying pure functions to effectful arguments**, with the precise form of effects that are permitted depending on the nature of the underlying functor.
|
834 |
"""
|
835 |
)
|
836 |
return
|
|
|
853 |
IO.pure(f) => IO(effect=f)
|
854 |
```
|
855 |
|
856 |
+
- `apply` should perform an action that produces a value, then apply the function with the value
|
857 |
|
858 |
The implementation is:
|
859 |
"""
|
|
|
872 |
|
873 |
@classmethod
|
874 |
def pure(cls, a):
|
|
|
875 |
return cls(a) if isinstance(a, Callable) else IO(lambda: a)
|
876 |
|
877 |
@classmethod
|
878 |
+
def apply(cls, fg, fa):
|
879 |
+
return cls.pure(fg.effect(fa.effect()))
|
|
|
880 |
return (IO,)
|
881 |
|
882 |
|
|
|
889 |
@app.cell
|
890 |
def _(IO):
|
891 |
def get_chars(n: int = 3):
|
892 |
+
return IO.sequenceL(
|
893 |
+
[IO.pure(input(f"input the {i}th str")) for i in range(1, n + 1)]
|
|
|
|
|
|
|
|
|
894 |
)
|
895 |
return (get_chars,)
|
896 |
|
897 |
|
898 |
@app.cell
|
899 |
def _():
|
900 |
+
# get_chars()()
|
901 |
return
|
902 |
|
903 |
|
|
|
1000 |
|
1001 |
```python
|
1002 |
pure(a) = fmap((lambda _: a), unit)
|
1003 |
+
apply(fg, fa) = fmap((lambda pair: pair[0](pair[1])), tensor(fg, fa))
|
1004 |
```
|
1005 |
|
1006 |
```python
|
1007 |
unit() = pure(())
|
1008 |
+
tensor(fa, fb) = lift(lambda fa: lambda fb: (fa, fb), fa, fb)
|
1009 |
```
|
1010 |
"""
|
1011 |
)
|
|
|
1053 |
cls, f: Callable[[A], B], ma: "ListMonoidal[A]"
|
1054 |
) -> "ListMonoidal[B]":
|
1055 |
return cls([f(a) for a in ma.items])
|
|
|
|
|
|
|
1056 |
return (ListMonoidal,)
|
1057 |
|
1058 |
|
1059 |
@app.cell(hide_code=True)
|
1060 |
def _(mo):
|
1061 |
+
mo.md(r"""> try with `ListMonoidal` below""")
|
1062 |
return
|
1063 |
|
1064 |
|
|
|
1070 |
return xs, ys
|
1071 |
|
1072 |
|
1073 |
+
@app.cell(hide_code=True)
|
1074 |
+
def _(mo):
|
1075 |
+
mo.md(r"""and we can prove that `tensor(fa, fb) = lift(lambda fa: lambda fb: (fa, fb), fa, fb)`:""")
|
1076 |
+
return
|
1077 |
+
|
1078 |
+
|
1079 |
+
@app.cell
|
1080 |
+
def _(List, xs, ys):
|
1081 |
+
List.lift(lambda fa: lambda fb: (fa, fb), List(xs.items), List(ys.items))
|
1082 |
+
return
|
1083 |
+
|
1084 |
+
|
1085 |
@app.cell(hide_code=True)
|
1086 |
def _(ABC, B, Callable, abstractmethod, dataclass):
|
1087 |
@dataclass
|
functional_programming/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1 |
# Changelog of the functional-programming course
|
2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
## 2025-04-06
|
4 |
|
5 |
- remove `sequenceL` from `Applicative` because it should be a classmethod but can't be generically implemented
|
|
|
1 |
# Changelog of the functional-programming course
|
2 |
|
3 |
+
## 2025-04-07
|
4 |
+
|
5 |
+
* the `apply` method of `Maybe` *Applicative* should return `None` when `fg` or `fa` is `None`
|
6 |
+
+ add `sequenceL` as a classmethod for `Applicative` and add examples for `Wrapper`, `Maybe`, `List`
|
7 |
+
+ add description for utility functions of `Applicative`
|
8 |
+
* refine the implementation of `IO` *Applicative*
|
9 |
+
* reimplement `get_chars` with `IO.sequenceL`
|
10 |
+
+ add an example to show that `ListMonoidal` is equivalent to `List` *Applicative*
|
11 |
+
|
12 |
## 2025-04-06
|
13 |
|
14 |
- remove `sequenceL` from `Applicative` because it should be a classmethod but can't be generically implemented
|