You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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):
1562
1565
1563
1566
```java
1564
1567
defn add(x,y) ^x + y;
1565
1568
1566
1569
0..9 ~fold add;
1567
-
// 45
1570
+
// 45 (0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9)
1568
1571
1569
1572
0..9 ~fold (acc,v) {
1570
1573
acc + v;
1571
1574
};
1572
-
// 45
1575
+
// 45 (0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9)
1573
1576
```
1574
1577
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).
1576
1587
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:
1578
1591
1579
1592
```java
1580
1593
defn sub(x,y) ^x - y;
1581
1594
1582
-
| ~fold 1..5, sub, 100 |;
1595
+
|~fold 1..5, 100, sub|;
1583
1596
// 85 (100 - 1 - 2 - 3 - 4 - 5)
1584
1597
```
1585
1598
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`.Thatreturn 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:
1587
1606
1588
1607
```java
1589
1608
defn onlyOdds(list,v)
1590
1609
![mod(v,2) ?=1]: list
1591
1610
^list +< v >
1592
1611
1593
-
| ~fold 0..9, onlyOdds, <> |;
1612
+
|~fold 0..9, <>, onlyOdds|;
1594
1613
// < 1, 3, 5, 7, 9 >
1595
1614
```
1596
1615
@@ -1606,7 +1625,7 @@ defn sub(x,y) ^x - y;
1606
1625
// -13 (1 - 2 - 3 - 4 - 5)
1607
1626
```
1608
1627
1609
-
### Monads
1628
+
### Monads (AndFriends)
1610
1629
1611
1630
The identity monad in **Foi** is called `Id`, and the empty monad is called `None`.
1612
1631
@@ -1667,13 +1686,13 @@ m ~. (double +> Id @); // Id{42}
1667
1686
1668
1687
**Note:**For convenience/familiarity sake, `~.` and `~chain` are both aliases for the `~bind` comprehension; all 3 are interchangable.
1669
1688
1670
-
`None` exposes a no-op `~.` bind operation:
1689
+
Comprehensionsfor`None` are all no-op operations, meaning they skip any behaviors and simply result in the same `None` value again:
1671
1690
1672
1691
```java
1673
1692
defn double(v) ^v *2;
1674
1693
1675
1694
Id@21~.double~fold log; // Id{42}
1676
-
None@ ~. double ~fold log; //
1695
+
None@~.double~fold log; // None
1677
1696
```
1678
1697
1679
1698
Neither the `double()` invocation nor the `log()` invocation will happen for the `None@` monadic value.
@@ -1783,7 +1802,9 @@ factorialM(5); // Id{120}
1783
1802
1784
1803
#### `Maybe` Monad
1785
1804
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:
1787
1808
1788
1809
```java
1789
1810
Maybe@42; // Id{42}
@@ -1793,8 +1814,6 @@ Id @ empty; // Id{empty}
1793
1814
None@; // None
1794
1815
```
1795
1816
1796
-
**Note:** `Maybe.None` is an alias for `None`, and `Maybe.Id` is an alias for `Id`.
1797
-
1798
1817
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:
1799
1818
1800
1819
```java
@@ -1831,11 +1850,15 @@ order
1831
1850
1832
1851
#### Foldable
1833
1852
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 mentionedFoldableearlier, with the `~fold` comprehension and lists (Tuples).We'll revisit the generalized Foldable behavior now, in the context of monads.
1835
1854
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.
1837
1856
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.
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 @`.
1855
1878
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:
1857
1884
1858
1885
```java
1859
1886
defn print(v) ^log("Value: " + v);
@@ -1865,7 +1892,7 @@ defn halve(v)
1865
1892
def e1: halve(0); // Left{"Value must be greater than 1"}
1866
1893
def e2: halve(4); // Right{2}
1867
1894
1868
-
e1 ~map print; //
1895
+
e1 ~map print; // Left{"Value must be greater than 1"}
1869
1896
e2 ~map print; // Value: 2
1870
1897
```
1871
1898
@@ -1877,6 +1904,8 @@ defn halve(v)
1877
1904
![v ?> 1]: Left @ "Value must be greater than 1"
1878
1905
![mod(v,2) ?= 0]: Right @ (v + 1) / 2
1879
1906
^Right @ v / 2;
1907
+
1908
+
// natural transformation utilities
1880
1909
defn maybeFromEither(e)
1881
1910
^| ~fold e, None@, Id@ |;
1882
1911
defn eitherFromMaybe(m)
@@ -1885,10 +1914,14 @@ defn eitherFromMaybe(m)
1885
1914
def m1: 0 #> halve #> maybeFromEither; // None
1886
1915
def m2: 4 #> halve #> maybeFromEither; // Id{2}
1887
1916
1888
-
|~fold eitherFromMaybe(m1), id, id | #> log; // Missing!
1917
+
| ~fold eitherFromMaybe(m1), id, id | #> log; // "Missing!"
1889
1918
| ~fold eitherFromMaybe(m2), id, id | #> log; // 2
1890
1919
```
1891
1920
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
+
1892
1925
### Type Annotations
1893
1926
1894
1927
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