2013-11-21 21:38:36 -08:00
|
|
|
^title Classes
|
2014-04-14 21:23:46 -07:00
|
|
|
^category types
|
2013-11-21 21:38:36 -08:00
|
|
|
|
2013-11-23 14:52:50 -08:00
|
|
|
Every value in Wren is an object, and every object is an instance of a class.
|
2015-01-03 23:27:02 -08:00
|
|
|
Even `true` and `false` are full-featured objects—instances of the `Bool`
|
|
|
|
|
class.
|
2013-11-23 14:52:50 -08:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
Classes contain both *behavior* and *state*. Behavior is defined in *methods*
|
|
|
|
|
which are stored in the class. State is defined in *fields*, whose values are
|
|
|
|
|
stored in each instance.
|
2013-12-28 09:37:41 -08:00
|
|
|
|
2013-11-23 14:52:50 -08:00
|
|
|
## Defining a class
|
|
|
|
|
|
|
|
|
|
Classes are created using the `class` keyword, unsurprisingly:
|
|
|
|
|
|
2014-01-20 21:44:51 -08:00
|
|
|
:::dart
|
2013-11-23 14:52:50 -08:00
|
|
|
class Unicorn {}
|
|
|
|
|
|
|
|
|
|
This creates a class named `Unicorn` with no methods or fields.
|
|
|
|
|
|
2014-02-04 08:45:08 -08:00
|
|
|
## Methods
|
2013-12-28 09:37:41 -08:00
|
|
|
|
2014-10-15 06:36:42 -07:00
|
|
|
To let our unicorn do stuff, we need to give it methods.
|
2013-12-28 09:37:41 -08:00
|
|
|
|
2014-01-20 21:44:51 -08:00
|
|
|
:::dart
|
2013-12-28 09:37:41 -08:00
|
|
|
class Unicorn {
|
|
|
|
|
prance {
|
2014-01-20 21:44:51 -08:00
|
|
|
IO.print("The unicorn prances in a fancy manner!")
|
2013-12-28 09:37:41 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
This defines a `prance` method that takes no arguments. To support parameters,
|
|
|
|
|
add a parenthesized parameter list after the method's name:
|
2013-12-28 09:37:41 -08:00
|
|
|
|
2014-01-20 21:44:51 -08:00
|
|
|
:::dart
|
2014-01-30 06:51:52 -08:00
|
|
|
class Unicorn {
|
|
|
|
|
prance(where, when) {
|
|
|
|
|
IO.print("The unicorn prances in " + where + " at " + when)
|
|
|
|
|
}
|
2013-12-28 09:37:41 -08:00
|
|
|
}
|
|
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
### Arity
|
|
|
|
|
|
|
|
|
|
Unlike most other dynamically-typed languages, in Wren you can have multiple
|
|
|
|
|
methods in a class with the same name, as long as they take a different number
|
|
|
|
|
of parameters. In technical terms, you can *overload by arity*. So this class
|
|
|
|
|
is fine:
|
2013-12-28 09:37:41 -08:00
|
|
|
|
2014-01-20 21:44:51 -08:00
|
|
|
:::dart
|
2013-12-28 09:37:41 -08:00
|
|
|
class Unicorn {
|
|
|
|
|
prance {
|
2014-01-20 21:44:51 -08:00
|
|
|
IO.print("The unicorn prances in a fancy manner!")
|
2013-12-28 09:37:41 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prance(where) {
|
2014-01-20 21:44:51 -08:00
|
|
|
IO.print("The unicorn prances in " + where)
|
2013-12-28 09:37:41 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prance(where, when) {
|
2014-01-20 21:44:51 -08:00
|
|
|
IO.print("The unicorn prances in " + where + " at " + when)
|
2013-12-28 09:37:41 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
And you can call each of the methods like so:
|
|
|
|
|
|
|
|
|
|
:::dart
|
|
|
|
|
var unicorn = new Unicorn
|
|
|
|
|
unicorn.prance
|
|
|
|
|
unicorn.prance("Antwerp")
|
|
|
|
|
unicorn.prance("Brussels", "high noon")
|
|
|
|
|
|
|
|
|
|
The number of arguments provided at the callsite determines which method is
|
|
|
|
|
chosen.
|
|
|
|
|
|
|
|
|
|
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 "undefined" or missing arguments. Wren just
|
|
|
|
|
treats them as different methods that you can implement separately.
|
|
|
|
|
|
|
|
|
|
### Operators
|
|
|
|
|
|
|
|
|
|
Operators are just special syntax for a method call on the left hand operand
|
|
|
|
|
(or only operand in the case of unary operators like `!` and `~`). In other
|
|
|
|
|
words, you can think of `a + b` as meaning `a.+(b)`.
|
|
|
|
|
|
|
|
|
|
You can define operators in your class like so:
|
|
|
|
|
|
|
|
|
|
:::dart
|
|
|
|
|
class Unicorn {
|
|
|
|
|
// Infix:
|
|
|
|
|
+(other) {
|
|
|
|
|
IO.print("Adding to a unicorn?")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prefix:
|
|
|
|
|
! {
|
|
|
|
|
IO.print("Negating a unicorn?!")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
This can be used to define any of these operators:
|
|
|
|
|
|
|
|
|
|
:::dart
|
|
|
|
|
// Infix:
|
|
|
|
|
+ - * / % < > <= >= == != & |
|
|
|
|
|
|
|
|
|
|
// Prefix:
|
|
|
|
|
! ~ -
|
|
|
|
|
|
|
|
|
|
Note that `-` can be both a prefix and infix operator. If there's a parameter
|
|
|
|
|
list, it's the infix one, otherwise, it's prefix. Since Wren supports
|
|
|
|
|
overloading by arity, it's no problem for a class to define both.
|
|
|
|
|
|
|
|
|
|
### Subscript operators
|
|
|
|
|
|
|
|
|
|
**TODO**
|
|
|
|
|
|
|
|
|
|
### Setters
|
|
|
|
|
|
|
|
|
|
**TODO**
|
2013-12-28 09:37:41 -08:00
|
|
|
|
|
|
|
|
## Constructors
|
|
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
To create a new instance of a class, you use the `new` keyword. We can make a
|
|
|
|
|
unicorn like so:
|
2014-02-04 08:45:08 -08:00
|
|
|
|
|
|
|
|
:::dart
|
|
|
|
|
new Unicorn
|
|
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
You almost always want to define some state or do some other initialization on
|
|
|
|
|
a new object. For that, you'll want to define a constructor, like so:
|
2014-02-04 08:45:08 -08:00
|
|
|
|
2014-04-08 21:18:17 -07:00
|
|
|
:::dart
|
2014-02-04 08:45:08 -08:00
|
|
|
class Unicorn {
|
|
|
|
|
new {
|
|
|
|
|
IO.print("I am a constructor!")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
When you create an instance with `new`, its constructor will be invoked. It's
|
|
|
|
|
just a method with a special name. Like methods, you can pass arguments to the
|
|
|
|
|
constructor by adding a parenthesized parameter list after `new`:
|
2014-02-04 08:45:08 -08:00
|
|
|
|
2014-04-08 21:18:17 -07:00
|
|
|
:::dart
|
2014-02-04 08:45:08 -08:00
|
|
|
class Unicorn {
|
|
|
|
|
new(name, color) {
|
|
|
|
|
IO.print("My name is " + name + " and I am " + color + ".")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Values are passed to the constructor like so:
|
|
|
|
|
|
2014-04-08 21:18:17 -07:00
|
|
|
:::dart
|
2014-02-04 08:45:08 -08:00
|
|
|
new Unicorn("Flicker", "purple")
|
2013-12-28 09:37:41 -08:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
Like other methods, you can overload constructors by [arity](#arity).
|
2014-04-08 21:18:17 -07:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
## Fields
|
2014-04-08 21:18:17 -07:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
All state stored in instances is stored in *fields*. Each field has a named
|
|
|
|
|
that starts with an underscore.
|
2014-04-08 21:18:17 -07:00
|
|
|
|
|
|
|
|
:::dart
|
2015-01-03 23:27:02 -08:00
|
|
|
class Rectangle {
|
|
|
|
|
area { _width * _height }
|
2014-04-08 21:18:17 -07:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
// Other stuff...
|
2014-04-08 21:18:17 -07:00
|
|
|
}
|
|
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
Here, `_width` and `_height` in the `area` [getter](classes.html#methods) refer
|
|
|
|
|
to fields on the rectangle instance. You can think of them like `this.width`
|
|
|
|
|
and `this.height` in other languages.
|
2014-04-08 21:18:17 -07:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
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](functions.html)
|
|
|
|
|
in a method. Wren will look outside any nested functions until it finds an
|
|
|
|
|
enclosing method.
|
2014-04-08 21:18:17 -07:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
Unlike [variables](variables.html), fields are implicitly declared by simply
|
|
|
|
|
assigning to them. If you access a field before it has been initialized, its
|
|
|
|
|
value is `null`.
|
2014-04-08 21:18:17 -07:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
### Encapsulation
|
2014-04-08 21:18:17 -07:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
All fields are *protected* in Wren—an object's fields can only be
|
|
|
|
|
directly accessed from within methods defined on the object's class, or its
|
|
|
|
|
superclasses. You cannot even access fields on another instance of your own
|
|
|
|
|
class, unlike C++ and Java.
|
2014-04-08 21:18:17 -07:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
If you want to make a property of an object visible, you need to define a
|
|
|
|
|
getter to expose it:
|
2014-04-08 21:18:17 -07:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
:::dart
|
|
|
|
|
class Rectangle {
|
|
|
|
|
width { _width }
|
|
|
|
|
height { _height }
|
2014-04-08 21:18:17 -07:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
// ...
|
|
|
|
|
}
|
2014-04-08 21:18:17 -07:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
To allow outside code to modify the field, you'll also need to provide setters:
|
2013-12-28 09:37:41 -08:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
:::dart
|
|
|
|
|
class Rectangle {
|
|
|
|
|
width=(value) { _width = value }
|
|
|
|
|
height=(value) { _height = value }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.
|
2013-12-28 09:37:41 -08:00
|
|
|
|
|
|
|
|
## Metaclasses and static members
|
|
|
|
|
|
|
|
|
|
**TODO**
|
2013-11-23 14:52:50 -08:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
### Static fields
|
|
|
|
|
|
|
|
|
|
A name that starts with *two* underscores is a *static* field. They work
|
|
|
|
|
similar to [fields](#fields) except the data is stored on the class itself, and
|
|
|
|
|
not the instance. They can be used in *both* instance and static methods.
|
|
|
|
|
|
|
|
|
|
**TODO: Example.**
|
|
|
|
|
|
|
|
|
|
|
2013-11-23 14:52:50 -08:00
|
|
|
## Inheritance
|
|
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
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.
|
2013-11-23 14:52:50 -08:00
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
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:
|
2013-11-23 14:52:50 -08:00
|
|
|
|
2014-01-20 21:44:51 -08:00
|
|
|
:::dart
|
2013-11-23 14:52:50 -08:00
|
|
|
class Pegasus is Unicorn {}
|
|
|
|
|
|
|
|
|
|
This declares a new class `Pegasus` that inherits from `Unicorn`.
|
|
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
The metaclass hierarchy does *not* parallel the regular class hierarchy. So, if
|
|
|
|
|
`Pegasus` inherits from `Unicorn`, `Pegasus`'s metaclass will not inherit from
|
|
|
|
|
`Unicorn`'s metaclass. In more prosaic terms, this means that static methods
|
|
|
|
|
are not inherited.
|
2013-12-28 09:37:41 -08:00
|
|
|
|
2014-10-15 06:36:42 -07:00
|
|
|
:::dart
|
|
|
|
|
class Unicorn {
|
|
|
|
|
// Unicorns cannot fly. :(
|
|
|
|
|
static canFly { false }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Pegasus is Unicorn {}
|
|
|
|
|
|
|
|
|
|
Pegasus.canFly // ERROR: Static methods are not inherited.
|
|
|
|
|
|
2015-01-03 23:27:02 -08:00
|
|
|
Constructors, however, initialize the instance *after* it has been created.
|
|
|
|
|
They are defined as instance methods on the class and not on the metaclass.
|
|
|
|
|
That means that constructors *are* inherited.
|
2013-12-28 09:37:41 -08:00
|
|
|
|
2014-10-15 06:36:42 -07:00
|
|
|
:::dart
|
|
|
|
|
class Unicorn {
|
|
|
|
|
new(name) {
|
|
|
|
|
IO.print("My name is " + name + ".")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Pegasus is Unicorn {}
|
|
|
|
|
|
|
|
|
|
new Pegasus("Fred") // Prints "My name is Fred.".
|