langur

functions declarations

Functions are pure unless declared impure.

Functions are first-order and can be anonymous. Both user-defined functions and built-in functions can be passed around as values.

The fn token designates a function definition.

Functions may be defined as shown in the examples below.

fn(.x, .y) { .x + .y } # 2 parameters fn(.y) { .x + .y } # 1 parameter and closure on 1 value fn() { .x + .y } # no parameters (closure on 2 values) fn{+} # operator implied function (2 implied parameters) fn{+3} # nil left partially implied function (1 implied parameter)

Curly braces are not required for a function declaration if the function body is a single statement (except for operator implied functions).

fn(.x, .y) .x + .y

function purity

Function impurity is explicit and requires the impure keyword.

impure fn(.x) { writeln .x }

Langur puts restrictions on the use of impure functions.

There are different kinds of impurity. There is the setting of values outside of function scope, and there are the other "side effects," such as writing to the console or a file. So far, langur makes no distintion between these things. It is nice to be able to explicitly set values outside of function scope at times, but langur has yet to have that ability.

closures

Functions defined to use values outside their boundaries are closures. After a closure is defined, it is not affected by changes to those outside values.

operator implied functions

Operator implied functions are defined without parentheses or spacing, using curly braces with an infix operator, such as fn{*}. This is the same as writing fn(.x, .y) .x * .y.

nil left partially implied functions

These are very similar to operator implied functions, but include a value on the right, such as fn{x 3} to multiply a passed value by 3 (likely used with map()).

parameter mutability

By default, function parameters are immutable.

To make a set of parameters mutable, precede the parameter names with a var token within the parameter list. To make the rest of the parameters immutable, precede the names with a val token.

fn(.x, .y) ... # .x and .y immutable

fn(var .x, .y) ... # .x and .y mutable

fn(var .x, val .y) ... # .x mutable and .y immutable

recursion

There are a couple of ways to do recursion. In the following example, the function definition is directly assigned to a variable.

val .fibonacci = fn(.x) if .x < 2 { .x } else { .fibonacci(.x - 1) + .fibonacci(.x - 2) }

This example function uses recursion and a single statement (if expression) without curly braces (braces shown are part of the if expression, not part of the function definition).

Recursion can also be used by making a call with the self token.

val .fibonacci = fn(.x) if .x < 2 { .x } else { self(.x - 1) + self(.x - 2) } # or shortened to... val .fibonacci = fn(.x) if(.x < 2: .x ; self(.x - 1) + self(.x - 2))

Using the self token, recursion does not require assignment (may be used with anonymous functions).

The built-in map(), fold(), and foldfrom() functions, or loops, can often be used instead of recursion.

return value

An explicit return can be used, but a function's last value is its implicit return value.

All functions return something, even if it's nothing (null).

function calls

User-defined functions can be called with parentheses, such as .x().

Built-in functions can be called with either parentheses or an unbounded argument list, such as split re/[!_]/, "a!b_c" which would split the string "a!b_c" into an list of 3 strings.

If you do not use an unbounded list or parentheses on a function name, it is not a call, but a reference to the function.

argument expansion

You can use a ... operator on the last argument passed to a function, to expand an list argument into multiple arguments.

zip(mapX(fn(... .x) .x, [7], [14, 21])...) # result == [7, 7, 14, 21]

val .x = [[21, 7], [42, 14], [96, 21]] zip(.x...) # result == [21, 42, 96, 7, 14, 21]

parameter expansion

You can use the ... operator for parameter expansion. This allows a user-defined function to receive a variable number of arguments. The last arguments of the function will be placed into a single list. This also allows for setting limits for this expansion (with a default range of 0 to unlimited). -1 indicates unlimited for this purpose. See the following examples.

val .a = fn(... .x) { } # accepts 0 or more arguments val .b = fn(...[1..9] .x) { } # accepts 1 to 9 arguments val .c = fn(.x, ...[4] .y) { } # accepts 5 arguments val .d = fn(.x, ...[2 .. -1] .y) { } # accepts 3 or more arguments val .e = fn(.x, ...[2 .. 6] .y) { } # accepts 3 to 7 arguments

unbounded lists

Unbounded lists can often be used to call built-in functions. They end at a line return or end of file, or closing parenthesis, curly brace, or square bracket. So, they are not bounded by their own set of parentheses.

val .n = random 10 # random() parameter list bounded by line return or end of file # same as val .n = random(10)

val .factorial = fn(.n) fold(fn{*}, series .n) # series() parameter list bounded by closing parenthesis of fold()

The following example contains no unbounded lists. It merely allows continuation of a list after a line return preceded by a comma.

val .new = foldfrom( fn(.hash, .key, .value) .hash ~ h{.key: .value}, h{}, fw/a b c d/, [1, 2, 3, 4], ) # use hash(fw/a b c d/, [1, 2, 3, 4]) instead, BTW

Unbounded lists end before a comma that precedes a line return.

# with .s already defined ... val .nums = map( fn(.i, .d) if(.i div 2: .d * 3; .d), series 13, map fn{-'0'}, s2cp .s, ) # unbounded list on series() ends before a comma preceding a line return # also note that both lists for s2cp() and the second call to map() end before a comma preceding a line return

An unbounded list takes all items it until it is closed. If an unbounded list is used within another, it takes the remaining items.

writeln "filtered: ", filter fn{div 2}, .arr # unbounded lists used with both filter() and writeln(); filter() takes last arguments (an anonymous function and a variable)

val .ns = string random 10 # generate a random number from 1 to 10 and save as a string to .ns # same as val .ns = string(random(10))

An unbounded list cannot be used in the test expression of an if expression (must use parentheses for a function call in this case).