No self-respecting language today can get by without functions—first class little bundles of code. Since Wren is object-oriented, most of your code will live in methods on classes, but free-floating functions are still eminently handy.
Functions are objects like everything else in Wren, instances of the Fn
class.
Most of the time you create a function just to pass it to some method. For
example, if you want to filter a list by some criteria, you’ll
call its where
method, passing in a function that defines the predicate
you’re filtering on.
Since that’s the most common usage pattern, Wren’s syntax optimizes for that. Taking a page from Ruby, a function is created by passing a block argument to a method. At its simplest, it looks like this:
blondie.callMe { System.print("This is the body!") }
Here we’re invoking the callMe
method on blondie
. We’re passing one
argument, a function whose body is the
following block—everything between that pair of
curly braces.
Methods that take a block argument receive it as a normal parameter. callMe
could be defined like so:
class Blondie { callMe(fn) { // Call it... } } var blondie = Blondie.new()
A method can take other arguments in addition to the block. They appear before the block just like a regular argument list. For example:
blondie.callMeAt(867, 5309) { System.print("This is the body!") }
Of course, you don’t have to use a block argument to pass a function to a method. If you already have a function object, you can pass it like a regular argument:
var someFn = // Get a function... blondie.callMe(someFn)
Block arguments are purely sugar for creating a function and passing it in one
little blob of syntax. There are some times when you want to create a function
but don’t need to pass it to a method. For that, you can call the Fn
class’s constructor:
var someFn = Fn.new { System.print("Hi!") }
As you can see it takes a block argument too! All the constructor does it return that, so this exists purely as a convenience method for you.
Once you have a function, how do you invoke it? Like everything in Wren, you do so by calling a method on it:
class Blondie { callMe(fn) { fn.call() } }
Functions expose a call()
method that executes the body of the function. This
method is dynamically-dispatched like any other, so you can define your own
“function-like” classes and pass them to methods that expect “real” functions.
class FakeFn { call() { System.print("I'm feeling functional!") } } blondie.callMe(FakeFn.new())
Of course, functions aren’t very useful if you can’t pass values to them. The
functions that we’ve seen so far take no arguments. To change that, you can
provide a parameter list surrounded by |
immediately after the opening brace
of the body, like so:
blondie.callMe {|first, last| System.print("Hi, " + first + " " + last + "!") }
Here we’re passing a function to greet
that takes two parameters, first
and
last
. They are passed to the function when it’s called:
class Blondie { callMe(fn) { fn.call("Debbie", "Harry") } }
It’s an error to call a function with fewer arguments than its parameter list expects. If you pass too many arguments, the extras are ignored.
The body of a function is a block. If it is a single
expression—more precisely if there is no newline after the {
or
parameter list—then the function implicitly returns the value of the
expression.
Otherwise, the body returns null
by default. You can explicitly return a
value using a return
statement. In other words, these two functions do the
same thing:
Fn.new { "return value" } Fn.new { return "return value" }
As you expect, functions are closures—they can access variables defined outside of their scope. They will hold onto closed-over variables even after leaving the scope where the function is defined:
class Counter { static create() { var i = 0 return Fn.new { i = i + 1 } } }
Here, the create
method returns the function created on its second line. That
function references a variable i
declared outside of the function. Even after
the function is returned from create
, it is still able to read and assign
toi
:
var counter = Counter.create() System.print(counter.call()) 1 System.print(counter.call()) 2 System.print(counter.call()) 3