Skip to content

Commit 501e097

Browse files
committed
README: added explicit section about lists/tuples and foldable, clarified details in various monad sub-sections
1 parent b52fe05 commit 501e097

File tree

1 file changed

+55
-22
lines changed

1 file changed

+55
-22
lines changed

README.md

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ The following is a partial exploration of what I've been imagining for awhile. T
7272
- [Function Pipelines](#function-pipelines)
7373
* [Loops and Comprehensions](#loops-and-comprehensions)
7474
- [Tagged Comprehensions](#tagged-comprehensions)
75-
* [Monads](#monads)
75+
- [Lists And Fold Comprehensions](#lists-and-fold-comprehensions)
76+
* [Monads (And Friends)](#monads-and-friends)
7677
- [The Monad Laws](#the-monad-laws)
7778
- [Do Syntax](#do-syntax)
7879
- [Monadic Function Returns](#monadic-function-returns)
@@ -1558,39 +1559,57 @@ def odds: 0..9 ~filter (v) {
15581559
// < 1, 3, 5, 7, 9 >
15591560
```
15601561
1561-
The `~fold` comprehension (left-to-right) works like this:
1562+
#### Lists And Fold Comprehensions
1563+
1564+
The `~fold` comprehension (left-to-right), often referred to as *reduce*, works like this with lists (Tuples):
15621565
15631566
```java
15641567
defn add(x,y) ^x + y;
15651568
15661569
0..9 ~fold add;
1567-
// 45
1570+
// 45 (0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9)
15681571
15691572
0..9 ~fold (acc,v) {
15701573
acc + v;
15711574
};
1572-
// 45
1575+
// 45 (0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9)
15731576
```
15741577
1575-
The result (and type) of the `~fold` comprehension is determined by the return value of the final *iteration* (`add()` above).
1578+
**Note:** The final result (and type) of the `~fold` comprehension is determined by the return value of the final *iteration* evaluation.
1579+
1580+
In this form of the `~fold` comprehension, the first *iteration* evaluation will receive the first value from the list (Tuple) as the *accumulator* (above: `x`, `acc`) argument, and the second value from the list as the *iteration-value* (above: `y`, `v`) argument.
1581+
1582+
So above, the first invocation of `add()` in each loop will have `x` set as `0` and `y` set as `1`. That return result (`1`) comes in as `x` for the second invocation of `add()`, with `y` being `2`. And so on.
1583+
1584+
----
1585+
1586+
But, folds/reductions sometimes need to start with a specific *initial value* rather than with just the first value in the list (Tuple). In this usage, we see the `~fold` comprehension as a representation of generalized [Foldable behavior](#foldable) (covered later).
15761587
1577-
The `~fold` comprehension accepts an optional third argument as an *initial-value* for the fold; however, this can only be provided in the evaluation-expression form:
1588+
If the `~fold` comprehension's *range* argument is a list (Tuple), and there are two other operands specified, then the second operand will be interpreted as the *initial-value* (or *default-value*), and the third operand as the *iteration* expression.
1589+
1590+
For example:
15781591

15791592
```java
15801593
defn sub(x,y) ^x - y;
15811594

1582-
| ~fold 1..5, sub, 100 |;
1595+
| ~fold 1..5, 100, sub |;
15831596
// 85 (100 - 1 - 2 - 3 - 4 - 5)
15841597
```
15851598

1586-
Folds *can* produce a Record/Tuple result. One common way to accomplish this is for the *initial-value* to be a Record/Tuple:
1599+
**Note:** As shown, 3 or more operands require the evaluation-expression form.
1600+
1601+
When an initial value is provided to a fold/reduce, it is set as the initial *accumulator* argument to the first *iteration*, and the first value in the list (Tuple) is the *iteration-value* argument.
1602+
1603+
So above, `100` is the *initial value*, and comes in as `x` in the first invocation of `sub()`, with `y` being the value `1`. That return result (`99`) comes in as `x` for the second invocation of `sub()`, with `y` being `2`. And so on.
1604+
1605+
Folds *can even* produce a Record/Tuple result. One common way to accomplish this is for the *initial-value* to be a Record/Tuple:
15871606

15881607
```java
15891608
defn onlyOdds(list,v)
15901609
![mod(v,2) ?= 1]: list
15911610
^list + < v >
15921611

1593-
| ~fold 0..9, onlyOdds, <> |;
1612+
| ~fold 0..9, <>, onlyOdds |;
15941613
// < 1, 3, 5, 7, 9 >
15951614
```
15961615

@@ -1606,7 +1625,7 @@ defn sub(x,y) ^x - y;
16061625
// -13 (1 - 2 - 3 - 4 - 5)
16071626
```
16081627

1609-
### Monads
1628+
### Monads (And Friends)
16101629

16111630
The identity monad in **Foi** is called `Id`, and the empty monad is called `None`.
16121631

@@ -1667,13 +1686,13 @@ m ~. (double +> Id @); // Id{42}
16671686

16681687
**Note:** For convenience/familiarity sake, `~.` and `~chain` are both aliases for the `~bind` comprehension; all 3 are interchangable.
16691688

1670-
`None` exposes a no-op `~.` bind operation:
1689+
Comprehensions for `None` are all no-op operations, meaning they skip any behaviors and simply result in the same `None` value again:
16711690

16721691
```java
16731692
defn double(v) ^v * 2;
16741693

16751694
Id @ 21 ~. double ~fold log; // Id{42}
1676-
None@ ~. double ~fold log; //
1695+
None@ ~. double ~fold log; // None
16771696
```
16781697

16791698
Neither the `double()` invocation nor the `log()` invocation will happen for the `None@` monadic value.
@@ -1783,7 +1802,9 @@ factorialM(5); // Id{120}
17831802

17841803
#### `Maybe` Monad
17851804

1786-
The `Id` and `None` monads are paired as the `Maybe` Sum type monad:
1805+
The `Id` and `None` monads are paired as the `Maybe` Sum type monad. For readability preferences, `Maybe.None` is an alias for `None`, and `Maybe.Id` is an alias for `Id`.
1806+
1807+
Consider:
17871808

17881809
```java
17891810
Maybe @ 42; // Id{42}
@@ -1793,8 +1814,6 @@ Id @ empty; // Id{empty}
17931814
None@; // None
17941815
```
17951816

1796-
**Note:** `Maybe.None` is an alias for `None`, and `Maybe.Id` is an alias for `Id`.
1797-
17981817
As show above, the `Maybe @` unit constructor selects `None` when it encounters the `empty` value, and `Id` for any other value. You can instead define custom constructor functions that select `None` or `Id` for various values:
17991818

18001819
```java
@@ -1831,11 +1850,15 @@ order
18311850

18321851
#### Foldable
18331852

1834-
**Foi** monads have Foldable behavior built-in, expressed with the `~fold` comprehension. As with other comprehension types, the *range* argument affects the context (and structure!) of the *iteration* expression of the comprehension.
1853+
We briefly mentioned Foldable earlier, with the `~fold` comprehension and lists (Tuples). We'll revisit the generalized Foldable behavior now, in the context of monads.
18351854
1836-
When *range* is a monadic value, the `~fold` comprehension requires two *iteration* expressions; because this is 3 operands, the evaluation-expression form is required.
1855+
Sum type monads in **Foi** have Foldable behavior built-in, expressed with the `~fold` comprehension. As with other comprehension types, the *range* argument affects the context (and structure!) of the *iteration* expression(s) of the comprehension.
18371856
1838-
For `None`, the *default iteration* expression (second operand, `"default!"`s below) is evaluated; for `Id`, the *alternate iteration* expression (third operand, `id`s below) is invoked.
1857+
For a monadic value *range*, the `~fold` comprehension requires multiple *iteration* expressions, one for each component of the Sum type; again, because this is 3 or more operands, the evaluation-expression form is required.
1858+
1859+
Let's consider a binary Sum type like `Maybe` (comprised of `None` and `Id`), which thus requires a *range* expression and **two** *iteration* expressions. For `None`, the *default iteration* expression (second operand, `"default!"`s below) is evaluated; for `Id`, the *alternate iteration* expression (third operand, `id`s below) is invoked.
1860+
1861+
For example:
18391862

18401863
```java
18411864
defn id(v) ^v;
@@ -1851,9 +1874,13 @@ def g: Maybe @ empty; // None
18511874
18521875
#### `Either` Monad
18531876
1854-
The `Either` monad is a Sum type, comprised of `Left` and `Right`. Additionally, `Either.Left` is an alias for `Left`, and `Either.Right` is an alias for `Right`.
1877+
The `Either` monad is another binary Sum type, comprised of `Left` and `Right`. For readability preferences, `Either.Left` is an alias for `Left`, and `Either.Right` is an alias for `Right`. Also, the `Either @` unit constructor is an alias for `Right @`.
18551878
1856-
`Left` is like `None`, except it can represent an affirmative value -- typically, an error message to indicate failure to perform an operation. `Right` is like `Id`.
1879+
`Either` is typically used for error flow-control in FP programs (as opposed to exception handling).
1880+
1881+
By holding an error message in a `Left` -- similar to `None`, except it can represent an affirmative value -- this error message can propagate through monadic operations (which are skipped), until the program handles the message. `Right` is like `Id`, in that it only holds some *success* value, and all its comprehensions are valid.
1882+
1883+
In this example, the error message specified for calling `halve(0)` sets `e1` as a `Left`, and thus its `~map` comprehension is a no-op:
18571884
18581885
```java
18591886
defn print(v) ^log("Value: " + v);
@@ -1865,7 +1892,7 @@ defn halve(v)
18651892
def e1: halve(0); // Left{"Value must be greater than 1"}
18661893
def e2: halve(4); // Right{2}
18671894
1868-
e1 ~map print; //
1895+
e1 ~map print; // Left{"Value must be greater than 1"}
18691896
e2 ~map print; // Value: 2
18701897
```
18711898
@@ -1877,6 +1904,8 @@ defn halve(v)
18771904
![v ?> 1]: Left @ "Value must be greater than 1"
18781905
![mod(v,2) ?= 0]: Right @ (v + 1) / 2
18791906
^Right @ v / 2;
1907+
1908+
// natural transformation utilities
18801909
defn maybeFromEither(e)
18811910
^| ~fold e, None@, Id@ |;
18821911
defn eitherFromMaybe(m)
@@ -1885,10 +1914,14 @@ defn eitherFromMaybe(m)
18851914
def m1: 0 #> halve #> maybeFromEither; // None
18861915
def m2: 4 #> halve #> maybeFromEither; // Id{2}
18871916
1888-
| ~fold eitherFromMaybe(m1), id, id | #> log; // Missing!
1917+
| ~fold eitherFromMaybe(m1), id, id | #> log; // "Missing!"
18891918
| ~fold eitherFromMaybe(m2), id, id | #> log; // 2
18901919
```
18911920
1921+
Above, `halve(0)` returns a `Left` holding the error message, which we then transform to a `Maybe` (`None`) with the `maybeFromEither()` utility. `halve(4)` produces a `Right` holding `2`, which when transformed to a `Maybe` becomes `Id` with `2`.
1922+
1923+
Then, for each instance of `Maybe`, we perform a *natural* transformation back to `Either`, with the `eitherFromMaybe()` utility. Finally, we fold the resulting `Either` instances, extracting the values `"Missing!"` and `2`, respectively.
1924+
18921925
### Type Annotations
18931926
18941927
Type annotations in **Foi** are applied to values/expressions (not to variables, etc). These are optional, as **Foi** uses type inference wherever possible. But applying them can often improve the performance optimizations the **Foi** compiler can produce. A type annotation always begins with the `as` keyword:

0 commit comments

Comments
 (0)