Spaces:
Running
Running
Commit
·
48feff6
1
Parent(s):
e3bcacf
refactor(applicatives): remove `sequenceL` from `Applicative` because it should be a classmethod but can't be generically implemented
Browse files
functional_programming/06_applicatives.py
CHANGED
@@ -7,9 +7,10 @@
|
|
7 |
|
8 |
import marimo
|
9 |
|
10 |
-
__generated_with = "0.12.
|
11 |
app = marimo.App(app_title="Applicative programming with effects")
|
12 |
|
|
|
13 |
@app.cell(hide_code=True)
|
14 |
def _(mo):
|
15 |
mo.md(
|
@@ -33,10 +34,9 @@ def _(mo):
|
|
33 |
/// details | Notebook metadata
|
34 |
type: info
|
35 |
|
36 |
-
version: 0.1.
|
37 |
|
38 |
///
|
39 |
-
|
40 |
"""
|
41 |
)
|
42 |
return
|
@@ -74,7 +74,7 @@ def _(mo):
|
|
74 |
def _(mo):
|
75 |
mo.md(
|
76 |
r"""
|
77 |
-
## Defining
|
78 |
|
79 |
As a result, we may want to define a single `Multifunctor` such that:
|
80 |
|
@@ -263,7 +263,7 @@ def _(mo):
|
|
263 |
|
264 |
- `apply` should apply a *wrapped* function to a *wrapper* value
|
265 |
|
266 |
-
The implementation is:
|
267 |
"""
|
268 |
)
|
269 |
return
|
@@ -379,7 +379,7 @@ def _(mo):
|
|
379 |
- if the function is `None`, apply returns `None`
|
380 |
- else apply the function to the value and wrap the result in `Just`
|
381 |
|
382 |
-
The implementation is:
|
383 |
"""
|
384 |
)
|
385 |
return
|
@@ -561,6 +561,10 @@ def _(mo):
|
|
561 |
r"""
|
562 |
## Utility functions
|
563 |
|
|
|
|
|
|
|
|
|
564 |
```python
|
565 |
@dataclass
|
566 |
class Applicative[A](Functor, ABC):
|
@@ -748,7 +752,7 @@ def _(
|
|
748 |
def _(mo):
|
749 |
mo.md(
|
750 |
r"""
|
751 |
-
# Effectful
|
752 |
|
753 |
Our original motivation for applicatives was the desire the 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.
|
754 |
|
@@ -777,7 +781,7 @@ def _(mo):
|
|
777 |
|
778 |
- `apply` should perform an action that produces a function and perform an action that produces a value, then call the function with the value
|
779 |
|
780 |
-
The implementation is:
|
781 |
"""
|
782 |
)
|
783 |
return
|
@@ -829,69 +833,6 @@ def _():
|
|
829 |
return
|
830 |
|
831 |
|
832 |
-
@app.cell(hide_code=True)
|
833 |
-
def _(mo):
|
834 |
-
mo.md(r"""## Collect the sequence of response with sequenceL""")
|
835 |
-
return
|
836 |
-
|
837 |
-
|
838 |
-
@app.cell(hide_code=True)
|
839 |
-
def _(mo):
|
840 |
-
mo.md(
|
841 |
-
r"""
|
842 |
-
One often wants to execute a sequence of commands and collect the sequence of their response, and we can define a function `sequenceL` for this
|
843 |
-
|
844 |
-
/// admonition
|
845 |
-
sequenceL actually means that
|
846 |
-
|
847 |
-
> execute a list of commands and collect the list of their response
|
848 |
-
///
|
849 |
-
"""
|
850 |
-
)
|
851 |
-
return
|
852 |
-
|
853 |
-
|
854 |
-
@app.cell
|
855 |
-
def _(A, Applicative):
|
856 |
-
def sequenceL(actions: list[Applicative[A]], ap) -> Applicative[list[A]]:
|
857 |
-
if not actions:
|
858 |
-
return ap.pure([])
|
859 |
-
|
860 |
-
return ap.lift(
|
861 |
-
lambda: lambda l1: lambda: lambda l2: list(l1) + list(l2),
|
862 |
-
actions[0],
|
863 |
-
sequenceL(actions[1:], ap),
|
864 |
-
)
|
865 |
-
return (sequenceL,)
|
866 |
-
|
867 |
-
|
868 |
-
@app.cell(hide_code=True)
|
869 |
-
def _(mo):
|
870 |
-
mo.md(
|
871 |
-
r"""
|
872 |
-
This function transforms a list of applicative actions into a single such action that returns a list of result values, and captures a common pattern of applicative programming.
|
873 |
-
|
874 |
-
And we can rewrite the `get_chars` more concisely:
|
875 |
-
"""
|
876 |
-
)
|
877 |
-
return
|
878 |
-
|
879 |
-
|
880 |
-
@app.cell
|
881 |
-
def _(IO, sequenceL):
|
882 |
-
def get_chars_sequenceL(n: int = 3):
|
883 |
-
return sequenceL(
|
884 |
-
[IO.pure(input(f"input the {i}th str") for i in range(1, n + 1))], IO
|
885 |
-
)
|
886 |
-
return (get_chars_sequenceL,)
|
887 |
-
|
888 |
-
|
889 |
-
@app.cell
|
890 |
-
def _():
|
891 |
-
# print(get_chars_sequenceL()())
|
892 |
-
return
|
893 |
-
|
894 |
-
|
895 |
@app.cell(hide_code=True)
|
896 |
def _(mo):
|
897 |
mo.md(r"""# From the perspective of category theory""")
|
@@ -904,7 +845,7 @@ def _(mo):
|
|
904 |
r"""
|
905 |
## Lax Monoidal Functor
|
906 |
|
907 |
-
An alternative, equivalent formulation of `Applicative` is given by
|
908 |
"""
|
909 |
)
|
910 |
return
|
@@ -928,12 +869,6 @@ def _(ABC, Functor, abstractmethod, dataclass):
|
|
928 |
return (Monoidal,)
|
929 |
|
930 |
|
931 |
-
@app.cell
|
932 |
-
def _(mo):
|
933 |
-
mo.md(r"""fmap g fa = fmap (g . snd) (unit ** fa)""")
|
934 |
-
return
|
935 |
-
|
936 |
-
|
937 |
@app.cell(hide_code=True)
|
938 |
def _(mo):
|
939 |
mo.md(
|
@@ -943,7 +878,7 @@ def _(mo):
|
|
943 |
- `unit` provides the identity element
|
944 |
- `tensor` combines two contexts into a product context
|
945 |
|
946 |
-
More technically, the idea is that `monoidal functor` preserves the "monoidal structure" given by the pairing constructor `(,)` and unit type `()`.
|
947 |
"""
|
948 |
)
|
949 |
return
|
@@ -991,7 +926,7 @@ def _(mo):
|
|
991 |
def _(mo):
|
992 |
mo.md(
|
993 |
r"""
|
994 |
-
## Mutual
|
995 |
|
996 |
We can implement `pure` and `apply` in terms of `unit` and `tensor`, and vice versa.
|
997 |
|
|
|
7 |
|
8 |
import marimo
|
9 |
|
10 |
+
__generated_with = "0.12.4"
|
11 |
app = marimo.App(app_title="Applicative programming with effects")
|
12 |
|
13 |
+
|
14 |
@app.cell(hide_code=True)
|
15 |
def _(mo):
|
16 |
mo.md(
|
|
|
34 |
/// details | Notebook metadata
|
35 |
type: info
|
36 |
|
37 |
+
version: 0.1.1 | last modified: 2025-04-06 | author: [métaboulie](https://github.com/metaboulie)<br/>
|
38 |
|
39 |
///
|
|
|
40 |
"""
|
41 |
)
|
42 |
return
|
|
|
74 |
def _(mo):
|
75 |
mo.md(
|
76 |
r"""
|
77 |
+
## Defining Multifunctor
|
78 |
|
79 |
As a result, we may want to define a single `Multifunctor` such that:
|
80 |
|
|
|
263 |
|
264 |
- `apply` should apply a *wrapped* function to a *wrapper* value
|
265 |
|
266 |
+
The implementation is:
|
267 |
"""
|
268 |
)
|
269 |
return
|
|
|
379 |
- if the function is `None`, apply returns `None`
|
380 |
- else apply the function to the value and wrap the result in `Just`
|
381 |
|
382 |
+
The implementation is:
|
383 |
"""
|
384 |
)
|
385 |
return
|
|
|
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 |
+
|
568 |
```python
|
569 |
@dataclass
|
570 |
class Applicative[A](Functor, ABC):
|
|
|
752 |
def _(mo):
|
753 |
mo.md(
|
754 |
r"""
|
755 |
+
# Effectful programming
|
756 |
|
757 |
Our original motivation for applicatives was the desire the 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.
|
758 |
|
|
|
781 |
|
782 |
- `apply` should perform an action that produces a function and perform an action that produces a value, then call the function with the value
|
783 |
|
784 |
+
The implementation is:
|
785 |
"""
|
786 |
)
|
787 |
return
|
|
|
833 |
return
|
834 |
|
835 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
836 |
@app.cell(hide_code=True)
|
837 |
def _(mo):
|
838 |
mo.md(r"""# From the perspective of category theory""")
|
|
|
845 |
r"""
|
846 |
## Lax Monoidal Functor
|
847 |
|
848 |
+
An alternative, equivalent formulation of `Applicative` is given by
|
849 |
"""
|
850 |
)
|
851 |
return
|
|
|
869 |
return (Monoidal,)
|
870 |
|
871 |
|
|
|
|
|
|
|
|
|
|
|
|
|
872 |
@app.cell(hide_code=True)
|
873 |
def _(mo):
|
874 |
mo.md(
|
|
|
878 |
- `unit` provides the identity element
|
879 |
- `tensor` combines two contexts into a product context
|
880 |
|
881 |
+
More technically, the idea is that `monoidal functor` preserves the "monoidal structure" given by the pairing constructor `(,)` and unit type `()`.
|
882 |
"""
|
883 |
)
|
884 |
return
|
|
|
926 |
def _(mo):
|
927 |
mo.md(
|
928 |
r"""
|
929 |
+
## Mutual definability of Monoidal and Applicative
|
930 |
|
931 |
We can implement `pure` and `apply` in terms of `unit` and `tensor`, and vice versa.
|
932 |
|
functional_programming/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1 |
# Changelog of the functional-programming course
|
2 |
|
|
|
|
|
|
|
|
|
3 |
## 2025-04-02
|
4 |
|
5 |
* `0.1.0` version of notebook `06_applicatives.py`
|
|
|
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
|
6 |
+
|
7 |
## 2025-04-02
|
8 |
|
9 |
* `0.1.0` version of notebook `06_applicatives.py`
|