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
|