Skip to content

Commit b52fe05

Browse files
committed
README: adding Maybe, Foldable, and Either monad sections
1 parent 31c209d commit b52fe05

File tree

1 file changed

+115
-7
lines changed

1 file changed

+115
-7
lines changed

README.md

Lines changed: 115 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ The following is a partial exploration of what I've been imagining for awhile. T
7676
- [The Monad Laws](#the-monad-laws)
7777
- [Do Syntax](#do-syntax)
7878
- [Monadic Function Returns](#monadic-function-returns)
79+
- [Maybe Monad](#maybe-monad)
80+
- [Foldable](#foldable)
81+
- [Either Monad](#either-monad)
7982
* [Type Annotations](#type-annotations)
8083

8184
### Imports
@@ -550,7 +553,7 @@ def greeting: ?(myName){
550553
greeting; // "Hello!"
551554
```
552555

553-
However, if no pattern matches, the default result of the expression is a Maybe::None -- **Foi** can be configured to issue a warning notice in such a case. More on monads later.
556+
However, if no pattern matches, the default result of the expression is a `empty` -- **Foi** can be configured to issue a warning notice in such a case. More on monads later.
554557

555558
To explicitly define a default pattern, use `?:` (which must be the last clause in the pattern matching expression):
556559

@@ -1145,7 +1148,7 @@ defn myFn(x) ![x ?> 10]: empty {
11451148

11461149
You can read/interpret the `![x ?> 10]: empty` pre-condition as: "x must be greater than 10; if it's not, return `empty` instead". That's basically the way we interpret pre-conditions in any programming language.
11471150

1148-
**Note:** In this usage, `empty` indicates to the calling code that the function had no valid computation to perform. However, there are other types of values that could (should!?) be returned here, such as a Maybe::None or an Either::Left. More on monads later.
1151+
**Note:** In this usage, `empty` indicates to the calling code that the function had no valid computation to perform. However, there are other types of values that could (should!?) be returned here, such as a `None` or `Left` monads (more later).
11491152

11501153
----
11511154

@@ -1611,7 +1614,7 @@ The `@` operator applies the "unit constructor" for any monad type, thus a monad
16111614
16121615
```java
16131616
def m: Id @ 42; // Id{42}
1614-
| @ Id 42 |; // Id{42}
1617+
| @ Id, 42 |; // Id{42}
16151618
16161619
def nil: None@; // None
16171620
| @ None |; // None
@@ -1637,7 +1640,6 @@ ofId(42); // Id{42}
16371640
A monadic value is a valid *range* for loops/comprehensions (`~map`, `~filter`, `~fold`, etc):
16381641
16391642
```java
1640-
defn id(v) ^v;
16411643
defn double(v) ^v * 2;
16421644
defn isEven(v) ^(mod(v,2) ?= 0)
16431645
def isOdd: !isEven;
@@ -1647,10 +1649,9 @@ def m: Id @ 21;
16471649
m ~map double; // Id{42}
16481650
m ~filter isOdd; // Id{21}
16491651
m ~filter isEven; // None
1650-
m ~fold id; // 21
16511652
```
16521653
1653-
**Note:** The `~map` comprehension expresses Functor behavior, and the `~fold` comprehension expresses Foldable behavior; these are related (but distinct) to monads and algebraic structures.
1654+
**Note:** The `~map` comprehension expresses Functor behavior, which is related (but distinct) to monads and other algebraic structures.
16541655
16551656
In addition to the standard comprehensions, monads (of course!) also can also be used with the `~bind` comprehension:
16561657
@@ -1715,7 +1716,6 @@ For completeness sake, let's illustrate the 3 monad laws using the `Id` monad, t
17151716
Composing multiple *bind* steps together can get hairy if subsequent steps need access to the results from earlier steps:
17161717

17171718
```java
1718-
defn id(v) ^v;
17191719
defn inc(v) ^v + 1;
17201720
defn double(v) ^v * 2;
17211721

@@ -1781,6 +1781,114 @@ defn factorialM(v,tot: Id @ 1) ![v ?> 1]: tot {
17811781
factorialM(5); // Id{120}
17821782
```
17831783
1784+
#### `Maybe` Monad
1785+
1786+
The `Id` and `None` monads are paired as the `Maybe` Sum type monad:
1787+
1788+
```java
1789+
Maybe @ 42; // Id{42}
1790+
Maybe @ empty; // None
1791+
1792+
Id @ empty; // Id{empty}
1793+
None@; // None
1794+
```
1795+
1796+
**Note:** `Maybe.None` is an alias for `None`, and `Maybe.Id` is an alias for `Id`.
1797+
1798+
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+
1800+
```java
1801+
defn nonZero(v)
1802+
?[v ?= 0]: None@
1803+
^Id @ v;
1804+
1805+
def qty: nonZero(0); // None
1806+
```
1807+
1808+
One common idiom where `Maybe` is used is conditional property access:
1809+
1810+
```java
1811+
defn prop(name)(obj) ^Maybe @ obj[name];
1812+
1813+
def order: Maybe @ <
1814+
shippingAddress: <
1815+
street: "123 Easy St",
1816+
city: "TX",
1817+
zip: "78889"
1818+
>
1819+
>;
1820+
1821+
order
1822+
~. prop("shippingAddress")
1823+
~. prop("street");
1824+
// Id{"123 Easy St"}
1825+
1826+
order
1827+
~. prop("billingAddress")
1828+
~. prop("street");
1829+
// None
1830+
```
1831+
1832+
#### Foldable
1833+
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.
1835+
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.
1837+
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.
1839+
1840+
```java
1841+
defn id(v) ^v;
1842+
1843+
def m: Maybe @ 42; // Id{42}
1844+
def g: Maybe @ empty; // None
1845+
1846+
| ~fold m, "default!", id |; // 42
1847+
| ~fold g, "default!", id |; // default!
1848+
```
1849+
1850+
**Note:** Folding a monadic value *usually* performs a *natural transformation* to another monadic type (via its unit constructor). Extracting or logging a value (as shown in these snippets) is a much less typical usage; that's merely convenient for illustration purposes here.
1851+
1852+
#### `Either` Monad
1853+
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`.
1855+
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`.
1857+
1858+
```java
1859+
defn print(v) ^log("Value: " + v);
1860+
defn halve(v)
1861+
![v ?> 1]: Left @ "Value must be greater than 1"
1862+
![mod(v,2) ?= 0]: Right @ (v + 1) / 2
1863+
^Right @ v / 2;
1864+
1865+
def e1: halve(0); // Left{"Value must be greater than 1"}
1866+
def e2: halve(4); // Right{2}
1867+
1868+
e1 ~map print; //
1869+
e2 ~map print; // Value: 2
1870+
```
1871+
1872+
As with `Maybe`, `Either` is foldable, so we can define *natural transformations* between them:
1873+
1874+
```java
1875+
defn id(v) ^v;
1876+
defn halve(v)
1877+
![v ?> 1]: Left @ "Value must be greater than 1"
1878+
![mod(v,2) ?= 0]: Right @ (v + 1) / 2
1879+
^Right @ v / 2;
1880+
defn maybeFromEither(e)
1881+
^| ~fold e, None@, Id@ |;
1882+
defn eitherFromMaybe(m)
1883+
^| ~fold m, Left @ "Missing!", Right@ |;
1884+
1885+
def m1: 0 #> halve #> maybeFromEither; // None
1886+
def m2: 4 #> halve #> maybeFromEither; // Id{2}
1887+
1888+
| ~fold eitherFromMaybe(m1), id, id | #> log; // Missing!
1889+
| ~fold eitherFromMaybe(m2), id, id | #> log; // 2
1890+
```
1891+
17841892
### Type Annotations
17851893

17861894
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)