Every value in Wren is an object, and every object is an instance of a class.
Even true
and false
are full-featured objects—instances of the
Bool class.
Classes define an objects behavior and state. Behavior is defined by methods which live in the class. Every object of the same class supports the same methods. State is defined in fields, whose values are stored in each instance.
Classes are created using the class
keyword, unsurprisingly:
class Unicorn {}
This creates a class named Unicorn
with no methods or fields.
To let our unicorn do stuff, we need to give it methods.
class Unicorn { prance() { System.print("The unicorn prances in a fancy manner!") } }
This defines a prance()
method that takes no arguments. To add parameters, put
their names inside the parentheses:
class Unicorn { prance(where, when) { System.print("The unicorn prances in %(where) at %(when).") } }
Since the number of parameters is part of a method’s signature a class can define multiple methods with the same name:
class Unicorn { prance() { System.print("The unicorn prances in a fancy manner!") } prance(where) { System.print("The unicorn prances in %(where).") } prance(where, when) { System.print("The unicorn prances in %(where) at %(when).") } }
It’s often natural to have the same conceptual operation work with different sets of arguments. In other languages, you’d define a single method for the operation and have to check for missing optional arguments. In Wren, they are different methods that you implement separately.
In addition to named methods with parameter lists, Wren has a bunch of other different syntaxes for methods. Your classes can define all of them.
A getter leaves off the parameter list and the parentheses:
class Unicorn { // Unicorns are always fancy. isFancy { true } }
A setter has =
after the name, followed by a single parenthesized parameter:
class Unicorn { rider=(value) { System.print("I am being ridden by %(value).") } }
By convention, the parameter is usually named value
but you can call it
whatever makes your heart flutter.
Prefix operators, like getters, have no parameter list:
class Unicorn { - { System.print("Negating a unicorn is weird.") } }
Infix operators, like setters, have a single parenthesized parameter for the right-hand operand:
class Unicorn { -(other) { System.print("Subtracting %(other) from a unicorn is weird.") } }
A subscript operator puts the parameters inside square brackets and can have more than one:
class Unicorn { [index] { System.print("Unicorns are not lists!") } [x, y] { System.print("Unicorns are not matrices either!") } }
Unlike with named methods, you can’t define a subscript operator with an empty parameter list.
As the name implies, a subscript setter looks like a combination of a subscript operator and a setter:
class Unicorn { [index]=(value) { System.print("You can't stuff %(value) into me at %(index)!") } }
Up to this point, “scope” has been used to talk exclusively about variables. In a procedural language like C, or a functional one like Scheme, that’s the only kind of scope there is. But object-oriented languages like Wren introduce another kind of scope: object scope. It contains the methods that are available on an object. When you write:
unicorn.isFancy
You’re saying “look up the method isFancy
in the scope of the object
unicorn
”. In this case, the fact that you want to look up a method
isFancy
and not a variable isFancy
is explicit. That’s what .
does and
the object to the left of the period is the object you want to look up the
method on.
this
#Things get more interesting when you’re inside the body of a method. When the
method is called on some object and the body is being executed, you often need
to access that object itself. You can do that using this
.
class Unicorn { name { "Francis" } printName() { System.print(this.name) //> Francis } }
The this
keyword works sort of like a variable, but has special behavior. It
always refers to the instance whose method is currently being executed. This
lets you invoke methods on “yourself”.
It’s an error to refer to this
outside of a method. However, it’s perfectly
fine to use it inside a function declared inside a method. When you do,
this
still refers to the instance whose method is being called:
class Unicorn { name { "Francis" } printNameThrice() { (1..3).each { // Use "this" inside the function passed to each(). System.print(this.name) //> Francis } //> Francis } //> Francis }
This is unlike Lua and JavaScript which can “forget” this
when you create a
callback inside a method. Wren does what you want here and retains the
reference to the original object.
(In technical terms, a function’s closure includes this
. Wren can do this
because it makes a distinction between methods and functions.)
this
#Using this.
every time you want to call a method on yourself works, but it’s
tedious and verbose, which is why some languages don’t require it. You can do a
“self send” by calling a method (or getter or setter) without any explicit
receiver:
class Unicorn { name { "Francis" } printName() { System.print(name) //> Francis } }
Code like this gets tricky when there is also a variable outside of the class with the same name. Consider:
var name = "variable" class Unicorn { name { "Francis" } printName() { System.print(name) // ??? } }
Should printName()
print “variable” or “Francis”? A method body has a foot in
each of two worlds. It is surrounded by the lexical scope where it’s defined in
the program, but it also has the object scope of the methods on this
.
Which scope wins? Every language has to decide how to handle this and there is a surprising plethora of approaches. Wren’s approach to resolving a name inside a method works like this:
this
.So, in the above example, we hit case #2 and it prints “Francis”. Distinguishing self sends from outer variables based on the case of the first letter in the name probably seems weird but it works surprisingly well. Method names are lowercase in Wren. Class names are capitalized.
Most of the time, when you’re in a method and want to access a name from outside of the class, it’s usually the name of some other class. This rule makes that work.
Here’s an example that shows all three cases:
var shadowed = "surrounding" var lowercase = "surrounding" var Capitalized = "surrounding" class Scope { shadowed { "object" } lowercase { "object" } Capitalized { "object" } test() { var shadowed = "local" System.print(shadowed) //> local System.print(lowercase) //> object System.print(Capitalized) //> surrounding } }
It’s a bit of a strange rule, but Ruby works more or less the same way.
We’ve seen how to define kinds of objects and how to declare methods on them. Our unicorns can prance around, but we don’t actually have any unicorns to do it. To create instances of a class, we need a constructor. You define one like so:
class Unicorn { construct new(name, color) { System.print("My name is " + name + " and I am " + color + ".") } }
The construct
keyword says we’re defining a constructor, and new
is its
name. In Wren, all constructors have names. The word “new” isn’t special to
Wren, it’s just a common constructor name.
To make a unicorn now, we call the constructor method on the class itself:
var fred = Unicorn.new("Fred", "palomino")
Giving constructors names is handy because it means you can have more than one, and each can clarify how it creates the instance:
class Unicorn { construct brown(name) { System.print("My name is " + name + " and I am brown.") } } var dave = Unicorn.brown("Dave")
Note that we have to declare a constructor because, unlike some other languages, Wren doesn’t give you a default one. This is useful because some classes aren’t designed to be constructed. If you have an abstract base class that just contains methods to be inherited by other classes, it doesn’t need and won’t have a constructor.
Like other methods, constructors can obviously have arguments, and can be overloaded by arity. A constructor must be a named method with a (possibly empty) argument list. Operators, getters, and setters cannot be constructors.
A constructor returns the instance of the class being created, even if you
don’t explicitly use return
. It is valid to use return
inside of a
constructor, but it is an error to have an expression after the return.
That rule applies to return this
as well, return handles that implicitly inside
a constructor, so just return
is enough.
return //> valid, returns 'this' return variable //> invalid return null //> invalid return this //> also invalid
A constructor is actually a pair of methods. You get a method on the class:
Unicorn.brown("Dave")
That creates the new instance, then it invokes the initializer on that instance. This is where the constructor body you defined gets run.
This distinction is important because it means inside the body of the
constructor, you can access this
, assign fields, call superclass
constructors, etc.
All state stored in instances is stored in fields. Each field has a name that starts with an underscore.
class Rectangle { area { _width * _height } // Other stuff... }
Here, _width
and _height
in the area
getter refer
to fields on the rectangle instance. You can think of them like this.width
and this.height
in other languages.
When a field name appears, Wren looks for the nearest enclosing class and looks up the field on the instance of that class. Field names cannot be used outside of an instance method. They can be used inside a function in a method. Wren will look outside any nested functions until it finds an enclosing method.
Unlike variables, fields are implicitly declared by simply
assigning to them. If you access a field before it has been initialized, its
value is null
.
All fields are private in Wren—an object’s fields can only be directly accessed from within methods defined on the object’s class.
In short, if you want to make a property of an object visible, you need to define a getter to expose it:
class Rectangle { width { _width } height { _height } // ... }
To allow outside code to modify the field, you need to provide setters to provide access:
class Rectangle { width=(value) { _width = value } height=(value) { _height = value } }
This might be different from what you’re used to, so here are two important facts:
Here is an example in code:
class Shape { construct new() { _shape = "none" } } class Rectangle is Shape { construct new() { //This will print null! //_shape from the parent class is private, //we are reading `_shape` from `this`, //which has not been set, so returns null. System.print("I am a %(_shape)") //a local variable, all variables are private _width = 10 var other = Rectangle.new() //other._width is not accessible from here, //even though we are also a rectangle. The field //is private, and other._width is invalid syntax! } } ...
One thing we’ve learned in the past forty years of software engineering is that encapsulating state tends to make code easier to maintain, so Wren defaults to keeping your object’s state pretty tightly bundled up. Don’t feel that you have to or even should define getters or setters for most of your object’s fields.
TODO
A name that starts with two underscores is a static field. They work similar to fields except the data is stored on the class itself, and not the instance. They can be used in both instance and static methods.
class Foo { construct new() {} static setFromStatic(a) { __a = a } setFromInstance(a) { __a = a } static printFromStatic() { System.print(__a) } printFromInstance() { System.print(__a) } }
Just like instance fields, static fields are initially null
:
Foo.printFromStatic() //> null
They can be used from static methods:
Foo.setFromStatic("first") Foo.printFromStatic() //> first
And also instance methods. When you do so, there is still only one static field shared among all instances of the class:
var foo1 = Foo.new() var foo2 = Foo.new() foo1.setFromInstance("second") foo2.printFromInstance() //> second
A class can inherit from a “parent” or superclass. When you invoke a method on an object of some class, if it can’t be found, it walks up the chain of superclasses looking for it there.
By default, any new class inherits from Object, which is the superclass from
which all other classes ultimately descend. You can specify a different parent
class using is
when you declare the class:
class Pegasus is Unicorn {}
This declares a new class Pegasus that inherits from Unicorn.
Note that you should not create classes that inherit from the built-in types (Bool, Num, String, Range, List). The built-in types expect their internal bit representation to be very specific and get horribly confused when you invoke one of the inherited built-in methods on the derived type.
The metaclass hierarchy does not parallel the regular class hierarchy. So, if Pegasus inherits from Unicorn, Pegasus’s metaclass does not inherit from Unicorn’s metaclass. In more prosaic terms, this means that static methods are not inherited.
class Unicorn { // Unicorns cannot fly. :( static canFly { false } } class Pegasus is Unicorn {} Pegasus.canFly //! Static methods are not inherited.
This also means constructors are not inherited:
class Unicorn { construct new(name) { System.print("My name is " + name + ".") } } class Pegasus is Unicorn {} Pegasus.new("Fred") //! Pegasus does not define new().
Each class gets to control how it may be constructed independently of its base classes. However, constructor initializers are inherited since those are instance methods on the new object.
This means you can do super
calls inside a constructor:
class Unicorn { construct new(name) { System.print("My name is " + name + ".") } } class Pegasus is Unicorn { construct new(name) { super(name) } } Pegasus.new("Fred") //> My name is Fred
TODO: Integrate better into page. Should explain this before mentioning super above.
Sometimes you want to invoke a method on yourself, but using methods defined in one of your superclasses. You typically do this in an overridden method when you want to access the original method being overridden.
To do that, you can use the special super
keyword as the receiver in a method
call:
class Base { method() { System.print("base method") } } class Derived is Base { method() { super.method() //> base method } }
You can also use super
without a method name inside a constructor to invoke a
base class constructor:
class Base { construct new(arg) { System.print("base got " + arg) } } class Derived is Base { construct new() { super("value") //> base got value } }
experimental stage: subject to minor changes
A class and methods within a class can be tagged with ‘meta attributes’.
Like this:
#hidden = true class Example {}
These attributes are metadata, they give you a way to annotate and store any additional information about a class, which you can optionally access at runtime. This information can also be used by external tools, to provide additional hints and information from code to the tool.
Since this feature has just been introduced, take note.
Currently there are no attributes with a built-in meaning. Attributes are user-defined metadata. This may not remain true as some may become well defined through convention or potentially through use by Wren itself.
Attributes are placed before a class or method definition,
and use the #
hash/pound symbol.
They can be
#key
on it’s own#key = value
#group(with, multiple = true, keys = "value")
An attribute key can only be a Name
. This is the same type of name
as a method name, a class name or variable name, an identifier that matches
the Wren identifier rules. A name results in a String value at runtime.
An attribute value can be any of these literal values: Name, String, Bool, Num
.
Values cannot contain expressions, just a value, there is no compile time
evaluation.
Groups can span multiple lines, methods have their own attributes, and duplicate keys are valid.
#key #key = value #group( multiple, lines = true, lines = 0 ) class Example { #test(skip = true, iterations = 32) doStuff() {} }
By default, attributes are compiled out and ignored.
For an attribute to be visible at runtime, mark it for runtime access using an exclamation:
#doc = "not runtime data" #!runtimeAccess = true #!maxIterations = 16
Attributes at runtime are stored on the class. You can access them via
YourClass.attributes
. The attributes
field on a class will
be null if a class has no attributes or if it’s attributes aren’t marked.
If the class contains class or method attributes, it will be an object with two getters:
YourClass.attributes.self
for the class attributesYourClass.attributes.methods
for the method attributesAttributes are stored by group in a regular Wren Map.
Keys that are not grouped, use null
as the group key.
Values are stored in a list, since duplicate keys are allowed, multiple values need to be stored. They’re stored in order of definition.
Method attributes are stored in a map by method signature, and each method
has it’s own attributes that match the above structure. The method signature
is prefixed by static
or foreign static
as needed.
Let’s see what that looks like:
// Example.attributes.self = // { // null: { "key":[null] }, // group: { "key":[value, 32, false] } // } #!key #ignored //compiled out #!group(key=value, key=32, key=false) class Example { #!getter getter {} // { regular(_,_): { null: { regular:[null] } } } #!regular regular(arg0, arg1) {} // { static other(): { null: { isStatic:[true] } } } #!isStatic = true static other() {} // { foreign static example(): { null: { isForeignStatic:[32] } } } #!isForeignStatic=32 foreign static example() }