Get rid of the separate opt-in IO class and replace it with a core System class. - Remove wren_io.c, wren_io.h, and io.wren. - Remove the flags that disable it. - Remove the overloads for print() with different arity. (It was an experiment, but I don't think it's that useful.) - Remove IO.read(). That will reappear using libuv in the CLI at some point. - Remove IO.time. Doesn't seem to have been used. - Update all of the tests, docs, etc. I'm sorry for all the breakage this causes, but I think "System" is a better name for this class (it makes it natural to add things like "System.gc()") and frees up "IO" for referring to the CLI's IO module.
4.8 KiB
^title Functions ^category types
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.
Block arguments
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:
:::dart
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:
:::dart
class Blondie {
callMe(fn) {
// Call it...
}
}
A method can take other arguments in addition to the block. They appear before the block just like a regular argument list. For example:
:::dart
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:
:::dart
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:
:::dart
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.
Calling functions
Once you have a function, how do you invoke it? Like everything in Wren, you do so by calling a method on it:
:::dart
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.
:::dart
class FakeFn {
call() {
System.print("I'm feeling functional!")
}
}
blondie.callMe(FakeFn.new())
Function parameters
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:
:::dart
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:
:::dart
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.
Returning values
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:
:::dart
Fn.new { "return value" }
Fn.new {
return "return value"
}
Closures
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:
:::dart
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:
:::dart
var counter = Counter.create
System.print(counter.call()) // Prints "1".
System.print(counter.call()) // Prints "2".
System.print(counter.call()) // Prints "3".