Spaces:
Runtime error
Runtime error
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, with the precise form of effects that are permitted depending on the nature of the underlying functor.
|
| 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
|