backup commit of notes so far

This commit is contained in:
2023-05-14 20:35:31 +01:00
parent 6a381c5b57
commit 33413b2b0f
34 changed files with 1109 additions and 421 deletions

View File

@@ -0,0 +1,128 @@
# Calculator
This example demonstrates various parts of the standard library, infix operators, `do{}` blocks, and various syntax elements. Approching MVP, this was the first benchmark created to debug various features. It predates the transition for `:=` from single-token macros to a dedicated language element.
```
import std::(parse_float, to_string)
import std::(readline, print)
export main := do{
cps data = readline;
let a = parse_float data;
cps op = readline;
cps print ("\"" ++ op ++ "\"\n");
cps data = readline;
let b = parse_float data;
let result = (
if op == "+" then a + b
else if op == "-" then a - b
else if op == "*" then a * b
else if op == "/" then a / b
else "Unsupported operation" -- dynamically typed shenanigans
);
cps print (to_string result ++ "\n");
0
}
```
## do
The main function uses a `do{}` block, which is processed using the following rules, temporarily added to the prelude:
```
export do { ...$statement ; ...$rest:1 } =0x2p543=> (
statement (...$statement) do { ...$rest }
)
export do { ...$return } =0x1p543=> (...$return)
```
This pair of rules converts the flat structure into a conslist which makes it easier for dedicated statement rules to process their own fragments. The produced structure looks roughly like this:
```
(statement (cps data = readline)
(statement (let a = parse_float data)
(statement (cps op = readline)
( ...
(statement (cps print (to_string result ++ "\n"))
(0)
)))))
```
`do` blocks contain semicolon-delimited statements which receive special handling, and a final expression that doesn't. This final expression must be present since every Orchid expression must produce a value including `do` blocks. For ergonomics, in the future a sentinel value may be returned if the body of the `do` block ends with a semicolon.
## statement
This example demonstrates three statement types. This collection can be extended by matching on `prelude::statement (<custom statement syntax>) ...$next`.
### let
`let` bindings are used for forward-declaring values in subsequent expressions, passing them to the rest of the body.
```
export statement (let $name = ...$value) ...$next =0x1p1000=> (
(\$name. ...$next) (...$value)
)
```
Since the executor keeps track of copies of the same expression and applies normalization steps to a shared instance, this technique also ensures that `...$value` will not be evaluated multiple times.
### cps=
`cps` was used for effectful functions.
```
export statement (cps $name = ...$operation) ...$next =0x2p1000=> (
(...$operation) \$name. ...$next
)
```
In the version of Orchid this example was written for, functions like `print` or `readline` carried out their work as a side effect of normalization. At this point the copy-tracking optimization described above wasn't used. Because of this, in new versions `print` or `readline` in a loop doesn't necessarily repeat its effect. This bug can be addressed in the standard library, but `cps` would still probably be just as useful.
### cps
Since `cps` is designed for side effects, an expression of this kind doesn't necessarily produce a value. This `=` free variant passes the tail as an argument to the expression as-is
```
export statement (cps ...$operation) ...$next =0x1p1000=> (
(...$operation) (...$next)
)
```
## if-then-else
This rule is substantially simpler, it simply forwards the three slots to a function that makes the actual decision.
```
export if ...$cond then ...$true else ...$false:1 =0x1p320=> (
ifthenelse (...$cond) (...$true) (...$false)
)
```
Notice that `else if` isn't a syntax element, it's simply an artifact of this rule applied to itself. The critical ordering requirement that enables this is that `cond` and `true` are squeezed so neither of them can accidentally consume an `if` or `else` token. `::prefix:0` is implied at the start, it is left of `cond:0` and `true:0` so it has a higher growth priority, and `false:1` has a higher explicit priority.
## Infix operators
Infix operators could be intuitively defined with something like the following
```
$lhs + $rhs =1=> (add $lhs $rhs)
$lhs * $rhs =2=> (mul $lhs $rhs)
```
However, if they really were defined this way, function application would have the lowest priority. Ideally, we would like function application to have the highest priority.
```
-- what we mean
(mult (parse_float "foobar") 2)
-- how we would like to write it
let a = parse_float "foobar" * 2
-- how we would have to write it
let a = (parse_float "foobar") * 2
```
With vectorial placeholders it's possible to define the operators in reverse, i.e. to match the "outermost" operator first.
```
...$lhs + ...$rhs =2=> (add (...$lhs) (...$rhs))
...$lhs * ...$rhs =1=> (mul (...$lhs) (...$rhs))
```
With this, function calls get processed before any operator.
## Dynamically typed shenanigans
If the operator character isn't recognized, `result` gets assigned `"Unsupported operation"`. This wouldn't work in most type systems as `result` is now either a string or a number with no static discriminator. Most of Orchid's functions accept a single type of input with the sole exception being `to_string`.

View File

@@ -0,0 +1,150 @@
This example showcases common list processing functions and some functional programming utilities. It is also the first multi-file demo.
_in main.orc_
```
import std::(to_string, print)
import super::list
import fn::*
export main := do{
let foo = list::new[1, 2, 3, 4, 5, 6];
let bar = list::map foo n => n * 2;
let sum = bar
|> list::skip 2
|> list::take 3
|> list::reduce 0 (a b) => a + b;
cps print $ to_string sum ++ "\n";
0
}
```
_in fn.orc_
```
export Y := \f.(\x.f (x x))(\x.f (x x))
export loop $r on (...$parameters) with ...$tail =0x5p512=> Y (\$r.
bind_names (...$parameters) (...$tail)
) ...$parameters
-- bind each of the names in the first argument as a parameter for the second argument
bind_names ($name ..$rest) $payload =0x2p1000=> \$name. bind_names (..$rest) $payload
bind_names () (...$payload) =0x1p1000=> ...$payload
export ...$prefix $ ...$suffix:1 =0x1p130=> ...$prefix (...$suffix)
export ...$prefix |> $fn ..$suffix:1 =0x2p130=> $fn (...$prefix) ..$suffix
export (...$argv) => ...$body =0x2p512=> (bind_names (...$argv) (...$body))
$name => ...$body =0x1p512=> (\$name. ...$body)
```
_in list.orc_
```
import option
import super::fn::*
pair := \a.\b. \f. f a b
-- Constructors
export cons := \hd.\tl. option::some (pair hd tl)
export end := option::none
export pop := \list.\default.\f. list default \cons.cons f
-- Operators
export reduce := \list.\acc.\f. (
loop r on (list acc) with
pop list acc \head.\tail. r tail (f acc head)
)
export map := \list.\f. (
loop r on (list) with
pop list end \head.\tail. cons (f head) (r tail)
)
export skip := \list.\n. (
loop r on (list n) with
if n == 0 then list
else pop list end \head.\tail. r tail (n - 1)
)
export take := \list.\n. (
loop r on (list n) with
if n == 0 then end
else pop list end \head.\tail. cons head $ r tail $ n - 1
)
new[...$item, ...$rest:1] =0x2p333=> (cons (...$item) new[...$rest])
new[...$end] =0x1p333=> (cons (...$end) end)
new[] =0x1p333=> end
export ::(new)
```
_in option.orc_
```
export some := \v. \d.\f. f v
export none := \d.\f. d
export map := \option.\f. option none f
export flatten := \option. option none \opt. opt
export flatmap := \option.\f. option none \opt. map opt f
```
The `main` function uses a `do{}` block to enclose a series of name bindings. It imports `list` as a sibling module and `fn` as a top-level file. These files are in identical position, the purpose of this is just to test various ways to reference modules.
## fn
### bind_names
This is a utility macro for binding a list of names on an expression. It demonstrates how to extract reusable macro program fragments to simplify common tasks. This demonstrative version simply takes a sequence of name tokens without any separators or custom programming, but its functionality can be extended in the future to include eg. destructuring.
### arrow functions
The arrow `=>` operator here is used to define inline functions. It is very similar to the native `\x.` lambda, except that native lambdas use higher priority than any macro so they can't appear inside a `do{}` block as all of the subsequent lines would be consumed by them. It is parsed using the following rules:
```
export (...$argv) => ...$body =0x2p512=> (bind_names (...$argv) (...$body))
$name => ...$body =0x1p512=> (\$name. ...$body)
```
### pipelines
This is a concept borrowed from Elixir. The `|>` operator simply inserts the output of the previous expression to the first argument of the following function.
```
export ...$prefix |> $fn ..$suffix:1 =0x2p130=> $fn (...$prefix) ..$suffix
```
It is processed left-to-right, but leaves the suffix on the same level as the function and sinks the prefix, which means that long pipelines eventually become left associative despite the inverted processing order.
### right-associative function call operator
The `$` operator is analogous to its Haskell counterpart. It is right-associative and very low priority. Its purpose is to eliminate trailing parentheses.
### Loop expression
Recursion in lambda calculus is achieved using a fixpoint combinator. The classic version of this combinator described by Church is the [Y-combinator][hb_tlc], defined like so:
```
export Y := \f.(\x.f (x x))(\x.f (x x))
```
[hb_tlc]: ISBN-0444867481
Formalizing what this does is difficult, in plain words it calls `f` with an expression that is equivalent to its own return value, thus giving the parameter a convenient means to define its value in terms of different parameterizations of itself. The following snippet computes 2^12 to demonstrate how it would normally be called.
```
export main := Y (\r.\n.\s.
if n == 0 then s
else r (n - 1) (s * 2)
) 12 0
```
The purpose of the loop expression is to provide a more convenient syntax to define recursive structures, as direct calls to the Y-combinator are error prone. It is defined as follows:
```
export loop $r on (...$parameters) with ...$tail =0x5p512=> Y (\$r.
bind_names (...$parameters) (...$tail)
) ...$parameters
```
The template allows the caller to give the point of recursion a name and enumerate the names that can change value between iterations of the loop. The point of recursion then has to be called with the same number of parameters.
It may be possible to construct a variant of this statement which allows only reassigning subsets of the mutable parameter list. It is definitely possible to construct a variant that allows declaring new names in place in the parameter list, although I did not have time to do so.