Skip to main content

Defining Functions

Raven functions are defined using the fn keyword.

test.rv
fn add(a, b) {
return a + b
}

println(add(2, 3)) # => 5

It's not always necessary to use return, because functions will implicitly return the last result. It's common to write short functions on one line.

test.rv
fn add(a, b) { a + b }

println(add(2, 3)) # => 5

Everything in Raven is an expression. Things that don't have another meaningful value – empty functions, for loops, println etc – will evaluate to nil.

test.rv
fn add(a, b) {}

println(add(2, 3)) # => nil

Plain return, without any value, is the same as return nil.

test.rv
fn add(a, b) {
return
}

println(add(2, 3)) # => nil

Splats

Dispatch

Functions can specify that they only support some kinds of objects. For example, + won't work with any old thing – we can't add two strings – so we can restrict add to numbers with a type annotation.

test.rv
fn add(a: Number, b: Number) {
return a + b
}

println(add(2, 3)) # => 5

Functions may have multiple signatures. Here's a version of add which can work with two strings, concatenating them.

test.rv
fn add(a: Number, b: Number) {
return a + b
}

fn add(a: String, b: String) {
return concat(a, b)
}

println(add(2, 3)) # => 5
println(add("foo", "bar")) # => foobar

If multiple methods match, they are tried in reverse order: last in, first out.

test.rv
fn foo(x: Number) {
"a number"
}

fn foo(x: Integer) {
"a round number"
}

println(foo(3.14)) # => a number
println(foo(2)) # => a round number

In general, more generic methods are defined first, and more specific ones later. For example, a collections library provides a shuffle function; a data structure then overrides shuffle to make it faster in that particular case.

Raven's function signatures are actually a powerful pattern matching system, of which x: T is one of the simplest cases. For example, here's a way to define the Fibonacci sequence.

test.rv
fn fib(n) { fib(n-1) + fib(n-2) }
fn fib(1) { 1 }
fn fib(0) { 0 }

println(fib(20)) # => 6765

Here are two equivalent ways to define the absolute value of a complex number, the second using destructuring.

test.rv
fn abs2(x: Complex) {
real(x)^2 + imag(x)^2
}

fn abs2(Complex(re, im)) {
re^2 + im^2
}

println(abs2(Complex(2, 3)))) # => 13

Pattern matching is covered in more detail later in the next section.

Swap Arguments

Almost everything in Raven is immutable (aka a value type). Instead of mutating information, in Raven we change variables, and the swap operator & helps us do that.

test.rv
fn increment(&x) {
x = x + 1
}

foo = 5
increment(&foo)
println(foo) # => 6

In fn increment(&x), the &x tells Raven that increment should implicitly return x when done. When we call increment(&foo), the &foo tells Raven to collect the new value and update foo.

This syntax helps us guarantee that our variables are only altered if we want them to be, even when they are more complex structures like dictionaries. If we instead write

bar = increment(foo)
println(foo) # => 5

then foo cannot be changed.

Here's a function that switches two variables.

test.rv
fn switch(&a, &b) {
[a, b] = [b, a]
return
}

a = 5
b = "foo"
switch(&a, &b)
println(a) # => foo
println(b) # => 5

After it compiles, this code is equivalent to

test.rv
fn switch(a, b) {
[a, b] = [b, a]
return [nil, a, b]
}

a = 5
b = "foo"
[result, a, b] = switch(a, b)
println(a)
println(b)