Skip to content

Commit 2c79013

Browse files
committed
README: adding sections on record/tuple equality, set equality, and the three monad laws
1 parent 3b1ddf2 commit 2c79013

File tree

1 file changed

+115
-13
lines changed

1 file changed

+115
-13
lines changed

README.md

Lines changed: 115 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ The following is a partial exploration of what I've been imagining for awhile. T
5555
* [Pattern Matching](#pattern-matching)
5656
- [Guard Expressions](#guard-expressions)
5757
* [Records and Tuples](#records-and-tuples)
58+
- [Equality Comparison](#equality-comparison)
5859
- [Inspecting](#inspecting)
5960
- [Generating Sequences (Ranges)](#generating-sequences-ranges)
6061
- [Deriving Instead Of Mutating](#deriving-instead-of-mutating)
@@ -72,6 +73,8 @@ The following is a partial exploration of what I've been imagining for awhile. T
7273
* [Loops and Comprehensions](#loops-and-comprehensions)
7374
- [Tagged Comprehensions](#tagged-comprehensions)
7475
* [Monads](#monads)
76+
- [The Monad Laws](#the-monad-laws)
77+
- [Do Syntax](#do-syntax)
7578
* [Type Annotations](#type-annotations)
7679

7780
### Imports
@@ -546,7 +549,7 @@ def greeting: ?(myName){
546549
greeting; // "Hello!"
547550
```
548551

549-
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.
552+
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.
550553

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

@@ -646,7 +649,9 @@ def myName: "Kyle";
646649

647650
### Records And Tuples
648651

649-
Records are immutable collections of values, delimited by `< >`. You can name each field of a record, but if you omit a name, numeric indexing is automatically applied. Any record with all numerically indexed fields (implicitly or explicitly defined) is a special case called a Tuple.
652+
Records are immutable collections of values, delimited by `< >`.
653+
654+
You can name each field of a record, but if you omit a name, numeric indexing is automatically applied. Any record with all numerically indexed fields (implicitly or explicitly defined) is a special case called a Tuple.
650655

651656
```java
652657
def idx: 2;
@@ -680,7 +685,7 @@ def person: < first: "Kyle", last: "Simpson" >;
680685
| . person, prop |; // "Simpson"
681686
```
682687

683-
To define Records/Tuples using arbitrary expressions for the values, use the evaluation-expression form:
688+
To define Records/Tuples using arbitrary expressions (other than simple identifiers) for the values, use the evaluation-expression form:
684689

685690
```java
686691
import uppercase from #Std.String;
@@ -692,7 +697,7 @@ def surname: "Simpson";
692697
def person: < first: "Kyle", last: |uppercase surname| >;
693698
```
694699

695-
To keep Record/Tuple syntax complexity to a minimum, *only* the `| |` form of evaluation-expression (function invocation, operators, etc) is allowed inside the `< >` literal definition.
700+
**Note:** To keep Record/Tuple syntax complexity to a minimum, *only* the `| |` form of evaluation-expression (function invocation, operators, etc) is allowed inside the `< >` literal definition.
696701

697702
Strings are just syntax sugar for tuples of characters. Once defined, a string and a tuple of characters will behave the same.
698703

@@ -750,9 +755,33 @@ As shown, the `<{ }>` *def-block* can contain any arbitrary logic for determi
750755

751756
Inside a `<{ }>` *def-block*, the `#` sigil indicates a self-reference to the current Record/Tuple context that's being defined, and can be used either in l-value (assignment target) or r-value (value expression) positions. However, these special self-references cannot cross inner-function boundaries.
752757

758+
#### Equality Comparison
759+
760+
Since Records/Tuples are primitive (and immutable) value types in **Foi**, equality comparison is structural (meaning deep contents comparison rather than reference identity).
761+
762+
```java
763+
def a: < 4, 5, 6 >;
764+
def b: < 4, 5, 6 >;
765+
def c: < 5, 4, 6 >;
766+
767+
a ?= b; // true
768+
b ?= c; // false
769+
c ?= a; // false
770+
```
771+
772+
```java
773+
def a: < one: "hello", two: "world" >;
774+
def b: < one: "hello", two: "world" >;
775+
def c: < two: "world", one: "hello" >;
776+
777+
a ?= b; // true
778+
b ?= c; // true
779+
c ?= a; // true
780+
```
781+
753782
#### Inspecting
754783

755-
You can determine if a value is in a Tuple with the `?in` / `!in` operator:
784+
You can determine if a value is *in* a Tuple with the `?in` / `!in` operator:
756785

757786
```java
758787
def numbers: < 4, 5, 6 >;
@@ -942,7 +971,7 @@ def uniques: <[ &something, &another ]>;
942971
// < 4, 5, 6, 7 >
943972
```
944973

945-
All syntax rules of Tuples `< >` still apply inside the `<[ ]>`, including use of the `&` and `%` sigils; as Sets are Tuples, not Records, field names are not allowed.
974+
All syntax rules of Tuple definition `< >` still apply inside the `<[ ]>`, including use of the `&` and `%` sigils; as Sets *are* Tuples, not Records, field names are not allowed.
946975

947976
The `+` operator, when both operands are Tuples, acts as a unique-only Set-append operation:
948977

@@ -956,6 +985,23 @@ moreNumbers; // < 4, 5, 6, 7 >
956985

957986
**Warning** The `+` operator only works on Tuples (Sets), not Records.
958987

988+
Set equality comparison deserves special attention. Since Sets are merely a construction form for Tuples, the `?=` will perform Tuple equality comparison, where order matters. This may produce undesired results (false negatives).
989+
990+
As such, the `?$=` (set equality) operator, and the corresponding `!$=` (set non-equality), perform unordered comparison of Sets (Tuples):
991+
992+
```java
993+
def set1: <[ 4, 5, 5, 6 ]>; // < 4, 5, 6 >
994+
def set2: <[ 5, 5, 6, 4 ]>; // < 5, 6, 4 >
995+
def set3: <[ 6, 4, 5, 0 ]>; // < 6, 4, 5, 0 >
996+
997+
set1 ?= set2; // false
998+
999+
set1 ?$= set2; // true
1000+
set1 !$= set3; // true
1001+
```
1002+
1003+
**Note:** Unordered (Set equality) comparison is slower than ordered comparison (Tuple equality). This cost is worth paying if you really need to compare two Sets, but it may be worth examining if a different approach is feasible.
1004+
9591005
### Functions
9601006

9611007
To define a function, use the `defn` keyword. To return a value from anywhere inside the function body, use the `^` sigil:
@@ -1098,7 +1144,7 @@ defn myFn(x) ![x ?> 10]: empty {
10981144

10991145
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.
11001146

1101-
**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.
1147+
**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.
11021148

11031149
----
11041150

@@ -1560,14 +1606,23 @@ defn sub(x,y) ^x - y;
15601606
15611607
The identity monad in **Foi** is called `Id`, and the empty monad is called `None`.
15621608
1563-
The `@` operator applies the "unit constructor" for any monad type, thus a monad value can be expressed like this:
1609+
The `@` operator applies the "unit constructor" for any monad type, thus a monadic value can be expressed like this:
15641610
15651611
```java
15661612
def m: Id @ 42; // Id{42}
15671613
| @ Id 42 |; // Id{42}
15681614
1569-
def nil: None@; // None{}
1570-
| @ None |; // None{}
1615+
def nil: None@; // None
1616+
| @ None |; // None
1617+
```
1618+
1619+
A monadic value is a primitive, immutable value type in **Foi**, meaning equality comparison is structural (just like Records/Tuples). As such:
1620+
1621+
```java
1622+
def m: Id @ 42;
1623+
def g: Id @ 42;
1624+
1625+
m ?= g; // true
15711626
```
15721627
15731628
If `@` is partially applied, you get a regular function for constructing a specific monad type:
@@ -1590,10 +1645,12 @@ def m: Id @ 21;
15901645
15911646
m ~map double; // Id{42}
15921647
m ~filter isOdd; // Id{21}
1593-
m ~filter isEven; // None{}
1648+
m ~filter isEven; // None
15941649
m ~fold id; // 21
15951650
```
15961651
1652+
**Note:** The `~map` comprehension expresses Functor behavior, and the `~fold` comprehension expresses Foldable behavior; these are related (but distinct) to monads and algebraic structures.
1653+
15971654
In addition to the standard comprehensions, monads (of course!) also can also be used with the `~bind` comprehension:
15981655
15991656
```java
@@ -1608,6 +1665,52 @@ m ~. (double +> Id @); // Id{42}
16081665
16091666
**Note:** For convenience/familiarity sake, `~.` and `~chain` are both aliases for the `~bind` comprehension; all 3 are interchangable.
16101667
1668+
`None` exposes a no-op `~.` bind operation:
1669+
1670+
```java
1671+
defn double(v) ^v * 2;
1672+
1673+
Id @ 21 ~. double ~fold log; // Id{42}
1674+
None@ ~. double ~fold log; //
1675+
```
1676+
1677+
Neither the `double()` invocation nor the `log()` invocation will happen for the `None@` monadic value.
1678+
1679+
#### The Monad Laws
1680+
1681+
For completeness sake, let's illustrate the 3 monad laws using the `Id` monad, the `@` unit-constructor, and the `~.` *bind* operator:
1682+
1683+
1. **Left Identity:**
1684+
1685+
```java
1686+
defn incM(v) ^(Id @ v + 1);
1687+
defn doubleM(v) ^(Id @ v * 2);
1688+
1689+
(Id @ 41) ~. incM; // Id{42}
1690+
```
1691+
1692+
2. **Right Identity:**
1693+
1694+
```java
1695+
Id @ 42 ~. (Id @);
1696+
// Id{42}
1697+
```
1698+
1699+
3. **Associativity:**
1700+
1701+
```java
1702+
defn incM(v) ^(Id @ v + 1);
1703+
defn doubleM(v) ^(Id @ v * 2);
1704+
1705+
Id @ 20 ~. incM ~. doubleM; // Id{42}
1706+
1707+
Id @ 20 ~. (v) {
1708+
incM(v) ~. doubleM;
1709+
}; // Id{42}
1710+
```
1711+
1712+
#### Do Syntax
1713+
16111714
Composing multiple *bind* steps together can get hairy if subsequent steps need access to the results from earlier steps:
16121715

16131716
```java
@@ -1619,8 +1722,7 @@ def incM: inc +> Id @;
16191722
def doubleM: double +> Id @;
16201723

16211724
def m:
1622-
incM(1)
1623-
~. (x) {
1725+
incM(1) ~. (x) {
16241726
doubleM(x) ~. (y) {
16251727
Id @ (3 * x) + y;
16261728
}

0 commit comments

Comments
 (0)