Continual improvement
This commit is contained in:
parent
541b6a51aa
commit
feb02dc1b6
45
language.md
45
language.md
|
@ -38,7 +38,7 @@ Ludus has a few different types of collections, in increasing order of complexit
|
|||
In all collection literals, members are written with a separator between them. On the same line, use a comma; or a newline will also separate elements. You may use as many separators as you wish at any point inside a collection or pattern. `(,,,,,,,3,,4,,,,,,)` and `(3, 4)` are the same value.
|
||||
|
||||
### Tuples
|
||||
Tuples are fully-imutable, ordered collections of any kinds of values, delimited by parentheses, e.g. `(1, :a, "foo")`. At current, they have no length limit (although they eventually will). Unlike in some languages, tuples can be empty or contain a single element: `()` and `(:foo)` are both just fine. Tuples largely cannot be manipulated functionally; they must be written as literals and unpacked using pattern matching. They can, however, be converted to lists, either through pattern matching or the `list` function. Their type is `:tuple`.
|
||||
Tuples are fully-immutable, ordered collections of any kinds of values, delimited by parentheses, e.g. `(1, :a, "foo")`. At current, they have no length limit (although they eventually will). Unlike in some languages, tuples can be empty or contain a single element: `()` and `(:foo)` are both just fine. Tuples largely cannot be manipulated functionally; they must be written as literals and unpacked using pattern matching. They can, however, be converted to lists, either through pattern matching or the `list` function. Their type is `:tuple`.
|
||||
|
||||
### Lists
|
||||
Lists are persistent and immutable ordered collections of any kinds of values, delimited by square braces, e.g. `[1, :a, "foo"]`. They are currently implemented using Clojure's persistent vectors. Their type is `:list`.
|
||||
|
@ -49,7 +49,7 @@ Lists may be combined using splats, written with ellipses, e.g., `[...foo, ...ba
|
|||
Sets are persistent and immutable unordered collections of any kinds of values, which can only contain one instance of any given value. They are written similarly to ordered collections: `${1, :a, "foo"}`. Their type is `:set`.
|
||||
|
||||
### Dictionaries, or dicts
|
||||
Dicts are persistent and immutable associative collections of any kinds of values. Dicts use keywords as keys (and cannot use any other kind of Ludus value as a key, not even strings), but can store any values. Dict literals are written as keyword-value pairs: `#{:a 1, :b false}`. Single words may be used as a shorthand for a key-value pair. Accessing a key that holds no value returns `nil`. Their type is `:dict`.
|
||||
Dicts are persistent and immutable associative collections of any kinds of values. Dicts use keywords as keys (and cannot use any other kind of Ludus value as a key, not even strings), but can store any values. Dict literals are written as keyword-value pairs: `#{:a 1, :b false}`. Single words may be used as a shorthand for a key-value pair. Accessing a key that holds no value returns `nil`. Their type is `:dict`.
|
||||
|
||||
### Namespaces
|
||||
Namespaces are immutable collections of bindings. They may only be described at the top level of a script. Accessing a key that has no value on a namepsace results in a panic. Their type is `:ns`.
|
||||
|
@ -70,7 +70,7 @@ Ludus names are bound permanently and immutably. How do you add something to a l
|
|||
Ludus provides functions that allow working with persistent collections. They're detailed in [the Prelude](/prelude.md). That said, all functions that modify collections take a collection and produce the modified collection _as a return value_, without changing the original collection. E.g., `append ([1, 2, 3], 4)` will produce `[1, 2, 3, 4]`. (For dicts, the equivalent is `assoc`.)
|
||||
|
||||
## Expressions
|
||||
Ludus is an expression-based language: all forms in the language are expressions and return values. That said, not all expressions may be used everywhere.
|
||||
Ludus is an expression-based language: all forms in the language are expressions and return values, except `panic!`. That said, not all expressions may be used everywhere.
|
||||
|
||||
### Toplevel expressions
|
||||
Some expressions may only be used in the "top level" of a script. Because they are the toplevel, they are assured to be statically knowable. These include: `ns`, `use`, `import`, and `test`.
|
||||
|
@ -150,7 +150,7 @@ Dict patterns may also use a splat as their last member: `let #{:a 1, ...b} = #{
|
|||
`let` bindings are the basic form of matching and binding in Ludus. It is written `let {pattern} = {non-binding expression}`. The pattern can be arbitrarily complex. If the left-hand side of a `let` binding does not match, Ludus will raise a panic, halting evaluation of the script.
|
||||
|
||||
## Scope and blocks
|
||||
Ludus is lexically scoped. Bindings are valid for the remainder of the scope they act on. To introduce a new scope, Ludus uses a block, a collection of expressions delimited by curly braces and separated by semicolons or newlines. The value of a block, as well as the value of a script, is the last expression in it. In `let foo = {:this; :set; :of; :expressions; "is actually"; :ignored }`, `foo` will be bound to `ignored`.
|
||||
Ludus is lexically scoped. Bindings are valid for the remainder of the scope they act on. To introduce a new scope, Ludus uses a block, a collection of expressions delimited by curly braces and separated by semicolons or newlines. The value of a block, as well as the value of a script, is the last expression in it. In `let foo = {:this; :set; :of; :expressions; "is actually"; :ignored }`, `foo` will be bound to `:ignored`.
|
||||
|
||||
That said, you can use bindings in blocks, which will not be available outside that block--but blocks can use bidnings from their parent scope:
|
||||
|
||||
|
@ -245,7 +245,7 @@ An anonymous lambda is written `fn {tuple pattern} -> {expression}`, `fn (x, y)
|
|||
A named function is identical to a lambda, with the one change that a word follows the `fn` reserved word: `fn {name} {tuple pattern} -> {expression}`. E.g., `fn add_1 (x) -> add (x, 1)`. The name of the function is bound for the remainder of the scope.
|
||||
|
||||
#### Compound functions
|
||||
Compound functions are functions that have multiple clauses. They must be named, and in place of a single clause, they consist in multiple clauses, separated by semicolons or newlines, delimited by curly braces. Optionally, compound functions may have a docstring, which explains the function's purpose and use, before any of the function clauses.
|
||||
Compound functions are functions that have multiple clauses. They must be named, and in place of a single clause after a name, they consist in one or more clauses, separated by semicolons or newlines, delimited by curly braces. Optionally, compound functions may have a docstring as their first element after the opening curly brace. The docstring explains the function's purpose and use, before any of the function clauses.
|
||||
|
||||
An exampele from Ludus's Prelude:
|
||||
|
||||
|
@ -271,13 +271,14 @@ Unary functions and called keywords may _not_ be partially applied: it is redund
|
|||
Because of "partial application," Ludus has a concept of an "argument tuple" (which may include a single placeholder) in addition to a tuple literal (which may not include a placeholder).
|
||||
|
||||
### Function pipelines, or `do` forms
|
||||
In place of nesting function calls inside other function calls, Ludus allows for a more streamlined version of function application: the `do` form or function pipeline:
|
||||
In place of nesting function calls inside other function calls, Ludus allows for a more streamlined version of function application: the `do` form or function pipeline. `do` is followed by an initial expression. `do` expressions use `>` as an operator: whatever is on the left hand side of the `>` is passed in as a single argument to whatever is on its right hand side. For example:
|
||||
|
||||
```
|
||||
let silly_result = do 23
|
||||
> mult (_, 2) > add (1, _)
|
||||
> sub (_, 2) > div (_, 9) & silly_result is 5
|
||||
```
|
||||
Newlines may appear before any instance of `>` in a `do` expression.
|
||||
|
||||
### Called keywords
|
||||
Keywords may be called as functions, in which case they extract the value stored at that key in the value passed in:
|
||||
|
@ -316,11 +317,13 @@ repeat 4 {
|
|||
Note that `repeat` does two interesting things:
|
||||
|
||||
1. It never returns a value other than `nil`. If it's in the block, it disappears.
|
||||
2. Unlike everything else in Ludus, it requires a block. You cannot write `repeat 4 forward (100)`. (Watch this space.)
|
||||
2. Unlike everything else in Ludus, it requires a block. You cannot write `repeat 4 forward! (100)`. (But: watch this space.)
|
||||
|
||||
### `loop`/`recur`
|
||||
`loop` and `recur` are largely identical to recursive functions for repetition, but use a special form to allow an anonymous construction and a few guard rails.
|
||||
|
||||
The syntax here is `loop <tuple> with { <function clauses> }`. (Or, you can have a single function clause instead of a set of clauses.) The tuple is passed in as the first set of arguments.
|
||||
|
||||
```
|
||||
let xs = [1, 2, 3, 4]
|
||||
loop (xs, 0) with {
|
||||
|
@ -329,8 +332,6 @@ loop (xs, 0) with {
|
|||
} &=> 10
|
||||
```
|
||||
|
||||
It is `loop <tuple> with { <function clauses> }`. (Or, you can have a single function clause instead of a set of clauses.)
|
||||
|
||||
`recur` is the recursive call. It must be in tail position--`recur` must be the root of a synthetic expression, in return position. (At present, this is not checked. It will be statically verified, eventually.)
|
||||
|
||||
`recur` calls return to the nearest `loop`. Nested `loop`s are probably a bad idea and should be avoided when possible.
|
||||
|
@ -345,17 +346,7 @@ The "toplevel" of a script are the expressions that are not embedded in other ex
|
|||
`use` loads the contents of a namespace into a script's context. To ensure that this is statically checkable, this must be at the toplevel.
|
||||
|
||||
### `ns`
|
||||
A namespace is an associative data structure that has some restrictions on it beyond dicts, which they resemble. `ns`es must be declared at the toplevel, to ensure static checkability. At current, accessing an absent member on an `ns` leads to a panic; eventually, it will be checked statically at compile-time. `ns`es must be named:
|
||||
|
||||
```
|
||||
ns foo {
|
||||
bar
|
||||
baz
|
||||
:quux 42
|
||||
}
|
||||
```
|
||||
|
||||
Typically, namespaces are the last thing exported in a script; an `import` then imports a namespace.
|
||||
Namespaces, `ns`es, may only be described at the toplevel of a script. This is to ensure they can be statically evaluatable.
|
||||
|
||||
### `test`
|
||||
A `test` expression (currently not working!--it will blow up your script, although it parses properly) is a way of ensuring things behave the way you want them to. Run the script in test mode (how?--that doesn't exist yet), and these are evaluated. If the expression under `test` returns a truthy value, you're all good! If the expression under `test` returns a falsy value or raises a panic, then Ludus will report which test(s) failed.
|
||||
|
@ -377,6 +368,8 @@ Formally: `test <string> <expression>`.
|
|||
## Changing things: `ref`s
|
||||
Ludus does not let you re-bind names. It does, however, have a type that allows for changing values over time: `ref` (short for reference--they are references to values). `ref`s are straightforward, but do require a bit more overhead than `let` bindings. The idea is that Ludus makes it obvious where mutable state is in a program, as well as where that mutable state may change. It does so elegantly, but with some guardrails that may take a little getting used to.
|
||||
|
||||
The type of a `ref` is, predictably, `:ref`.
|
||||
|
||||
```
|
||||
ref foo = 42 & foo is now bound to a _ref that contains 42_
|
||||
add (1, foo) & panic! no match: foo is _not_ a number
|
||||
|
@ -386,7 +379,7 @@ value_of (foo) &=> 23; use value_of to get the value contained in a ref
|
|||
```
|
||||
|
||||
### Ending with a bang!
|
||||
Ludus has a strong naming convention that functions that change state or could cause a panic end in an exclamation point (or, in computer nerd parlance, a "bang"). So anything function that mutates the value held in a reference ends with a bang: `make!` and `update!` take bangs; `value_of` does not.
|
||||
Ludus has a strong naming convention that functions that change state or could cause an explicit panic end in an exclamation point (or, in computer nerd parlance, a "bang"). So anything function that mutates the value held in a reference ends with a bang: `make!` and `update!` take bangs; `value_of` does not.
|
||||
|
||||
This convention also includes anything that prints to the console: `print!`, `report!`, `doc!`, `update!`, `make!`, etc.
|
||||
|
||||
|
@ -404,7 +397,15 @@ A special function, `panic!`, halts script execution with any arguments output a
|
|||
|
||||
In fact, the only functions in the Prelude which can cause panics are, at current, `div`, `unwrap!`, and `assert!`. And, of course, `panic!` itself.
|
||||
|
||||
### `nil`s, not errors
|
||||
Ludus, however, tries to return `nil` instead of panicking a lot of the time. So, for example, attempting to get access a value at a keyword off a number, while nonsensical, will return `nil` rather than panicking:
|
||||
|
||||
```
|
||||
let a = true
|
||||
a :b :c :d :e &=> nil
|
||||
```
|
||||
|
||||
### Result tuples
|
||||
Instead of exceptions or special error values, recoverable errors in Ludus are handled instead by result tuples: `(:ok, value)` and `(:err, msg)`. So, for example, `unwrap!` takes a result tuple and either returns the value in the `:ok` case, or panics in the `:err` case.
|
||||
Operations that could fail--especially when you want some information about why--don't always return `nil` on failures. Instead of exceptions or special error values, recoverable errors in Ludus are handled instead by result tuples: `(:ok, value)` and `(:err, msg)`. So, for example, `unwrap!` takes a result tuple and either returns the value in the `:ok` case, or panics in the `:err` case.
|
||||
|
||||
Variants of some functions that may have undesirably inexplicit behaviour are written as `{name}/safe`. So, for example, you can get a variant of `div` that returns a result tuple in `div/safe`, which returns `(:ok, result)` when everything's good; and `(:err, "division by zero")` when the divisor is 0. Or, `get/safe` will give you a result rather than returning `nil`. (Althougn `nil` punning makes this mostly unncessary. Mostly.)
|
||||
|
|
|
@ -85,6 +85,7 @@ fn at {
|
|||
gte? (n, count (xs)) -> nil
|
||||
else -> base :nth (xs, inc (n))
|
||||
}
|
||||
(_) -> nil
|
||||
}
|
||||
|
||||
fn first {
|
||||
|
|
Loading…
Reference in New Issue
Block a user