First, assignment can be done lazily or eagerly. Imperative languages (most popular languages nowadays) assign values eagerly, which means that when you say a = b you're really defining a as the current value of b. If b changes, a will remain the same. With lazy evaluation, a is simply defined as the expression b, which will only be evaluated when a is needed. You can do things like this that would raise exceptions in imperative languages:
>> a = b
>> b = 6
>> a
6
(Note: >> designates a line entered in the interpreter. Anything beneath is the value returned.)
So, variables can be defined in an order-agnostic way. I deviate from absolute pure functional languages like Haskell in that variables and functions are allowed to be redefined; in practical applications, some state is unavoidable, so Scotch allows state without having to pull teeth.
Pattern matching is also a really cool way to represent functions. Say, for example, we're constructing a factorial function (not especially useful, but I like to use this as an easy example.) Based on the definition of factorial, we can define it like this:
fact(n) = n * fact(n-1)
The result is a recursive function that calls itself with smaller and smaller values of n. Unfortunately, this function will continue forever, calling itself with values down to negative infinity. How do we tell it to end? In an imperative language, you could use if, like so:
fact(n) = if n == 0 then 1 else n * fact(n-1)
But, with pattern matching, we can define the function differently when the value of n is 0, like this:
fact(n) = n * fact(n-1)
fact(0) = 1
When the function is called, it checks the most recent definition to see if it's a match: if n is 0, it will match and return 1. If n is not 0, it will match to the next definition, where n can be anything. Not only is this version shorter, but to me, the meaning is much clearer here - and as the complexity of the function increases beyond that of a simple factorial function, the benefits to readability become more obvious.
Just about the entire Scotch standard library is written in this fashion, using (tail) recursive functions. Any function, from getting the length of a string to getting the rightmost n characters, can usually be represented as a general definition plus a base case that tells the recursion when to stop.
Patterns can also be used to split lists and strings. For example,
head(h:t) = h
tail(h:t) = t
In this example, the pattern is two variables, separated by a colon. When a list or string is passed as an argument to these functions, the first element/character becomes h, and the rest becomes t. This means you can do what you want with h, then call the same function on the rest of the list/string, to manipulate a string or list item by item.
Finally, in Scotch, functions are values, and so are partially applied functions. Let's look at a few examples of passing functions as arguments to other functions:
>> apply(f, x) = f(x)
>> g(n) = n
>> apply(g, 10)
10
The function g is passed as an argument to the function apply, and then called with the argument 10, returning g(10) which equals 10.
Partially applied functions are function calls to which we will add more arguments later. For example:
>> apply(f, x) = f(x)
>> add(x, y) = x + y
>> apply(add(10), 20)
30
Normally, add(10) wouldn't be a valid function call, because there's no definition of the add function that only takes one argument. But in this case, add(10), a partially applied function, is called with another argument, 20, resulting in add(10, 20), or 30.
For developers coming from imperative languages, imperative-style procedures ("procs") can be defined like so:
print_number(n) = do a = "The number is " + n;
print a;
This capability relieves a bit of a pain point with pure functional languages such as Haskell, the use of monads to handle IO. Here, IO can be interspersed within regular code, which, among other things, makes quick debugging much easier.
Notice, also, that Scotch is weakly typed, meaning a number "n" can be added to a string without having to explicitly convert it to a string.
Anyway, that's Scotch so far. There are still some things that need to be implemented - notably concurrency, systems programming, and non-file I/O.