Defining Functions
Raven functions are defined using the fn
keyword.
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.
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
.
fn add(a, b) {}
println(add(2, 3)) # => nil
Plain return
, without any value, is the same as return nil
.
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.
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.
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.
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.
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.
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.
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.
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
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)