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
Copy file name to clipboardExpand all lines: README.md
+115-7Lines changed: 115 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -76,6 +76,9 @@ The following is a partial exploration of what I've been imagining for awhile. T
76
76
-[The Monad Laws](#the-monad-laws)
77
77
-[Do Syntax](#do-syntax)
78
78
-[Monadic Function Returns](#monadic-function-returns)
79
+
-[Maybe Monad](#maybe-monad)
80
+
-[Foldable](#foldable)
81
+
-[Either Monad](#either-monad)
79
82
*[Type Annotations](#type-annotations)
80
83
81
84
### Imports
@@ -550,7 +553,7 @@ def greeting: ?(myName){
550
553
greeting; // "Hello!"
551
554
```
552
555
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.
554
557
555
558
To explicitly define a default pattern, use `?:` (which must be the last clause in the pattern matching expression):
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.
1147
1150
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).
1149
1152
1150
1153
----
1151
1154
@@ -1611,7 +1614,7 @@ The `@` operator applies the "unit constructor" for any monad type, thus a monad
1611
1614
1612
1615
```java
1613
1616
def m: Id @ 42; // Id{42}
1614
-
| @ Id 42 |; // Id{42}
1617
+
| @ Id, 42 |; // Id{42}
1615
1618
1616
1619
def nil: None@; // None
1617
1620
| @ None |; // None
@@ -1637,7 +1640,6 @@ ofId(42); // Id{42}
1637
1640
A monadic value is a valid *range* for loops/comprehensions (`~map`, `~filter`, `~fold`, etc):
1638
1641
1639
1642
```java
1640
-
defn id(v) ^v;
1641
1643
defn double(v) ^v * 2;
1642
1644
defn isEven(v) ^(mod(v,2) ?= 0)
1643
1645
def isOdd: !isEven;
@@ -1647,10 +1649,9 @@ def m: Id @ 21;
1647
1649
m ~map double; // Id{42}
1648
1650
m ~filter isOdd; // Id{21}
1649
1651
m ~filter isEven; // None
1650
-
m ~fold id; // 21
1651
1652
```
1652
1653
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.
1654
1655
1655
1656
In addition to the standard comprehensions, monads (of course!) also can also be used with the `~bind` comprehension:
1656
1657
@@ -1715,7 +1716,6 @@ For completeness sake, let's illustrate the 3 monad laws using the `Id` monad, t
1715
1716
Composing multiple *bind* steps together can get hairy if subsequent steps need access to the results from earlier steps:
1716
1717
1717
1718
```java
1718
-
defn id(v) ^v;
1719
1719
defn inc(v) ^v +1;
1720
1720
defn double(v) ^v *2;
1721
1721
@@ -1781,6 +1781,114 @@ defn factorialM(v,tot: Id @ 1) ![v ?> 1]: tot {
1781
1781
factorialM(5); // Id{120}
1782
1782
```
1783
1783
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
+
1784
1892
### TypeAnnotations
1785
1893
1786
1894
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