Structs
A struct defines a type that bundles data with the methods that work on it.
Defining a struct
Section titled “Defining a struct”struct Point fn new(x, y) @x = x @y = y end
fn distance_to(other) dx = other.x - @x dy = other.y - @y math::sqrt((dx * dx + dy * dy).to_float()) end
fn describe() "(${@x}, ${@y})" endend
a = Point.new(0, 0)b = Point.new(3, 4)println a.distance_to(b) # 5println b.describe() # (3, 4)newis the constructor.StructName.new(...)creates an instance and calls it. A struct without anewmethod can still be instantiated withStructName.new().@namereads or writes an instance variable of the current object. Instance variables are created by assignment, usually innew.- Methods call each other directly, as in
twice()below, or through@, the current instance.
Instance variables
Section titled “Instance variables”Instance variables are visible from outside as properties, and can be assigned there too:
struct Account fn new(owner) @owner = owner @balance = 0 endend
acct = Account.new("Ada")println acct.owner # Adaacct.balance = 50println acct.balance # 50Prefer methods for anything with rules attached, so callers don’t need to know the internals.
Instance variables in lambdas
Section titled “Instance variables in lambdas”A lambda created in a method keeps that method’s @, so it can read and update the instance:
struct Pricing fn new(rate) @rate = rate end
fn apply(prices) prices.map(do (p) => p * @rate) endend
println Pricing.new(2).apply([1, 5]) # [2, 10]Methods that return @
Section titled “Methods that return @”Returning @ from a method allows chained calls:
struct Builder fn new() @parts = [] end
fn add(part) @parts.push(part) @ end
fn count() @parts.size() end
fn twice() count() * 2 end
fn build() @parts.join(" ") endend
b = Builder.new().add("kiwi").add("is").add("tasty")println b.build() # kiwi is tastyprintln b.twice() # 6Converting to a string
Section titled “Converting to a string”Define to_string() to give a struct a readable form. println, string interpolation and printing a list or hashmap that holds the instance all use it. Without one, an instance prints in the generic <struct(name=Money, identifier=Money)> form.
struct Money fn new(cents) @cents = cents end
fn to_string() "$" + (@cents / 100.0).to_string("f2") endend
price = Money.new(1999)println price # $19.99println "Total: ${price}" # Total: $19.99println [price, Money.new(5)] # [$19.99, $0.05]Callable instances
Section titled “Callable instances”An instance whose struct defines a call method can be called like a function: obj(x) is obj.call(x). Positional, named and splatted arguments all bind to call’s parameters. Code that calls a value doesn’t need to know whether it holds a lambda or an object, which is how web treats apps.
struct Multiplier fn new(factor) @factor = factor end
fn call(x) x * @factor endend
triple = Multiplier.new(3)println triple(5) # 15
double = with (x) do x * 2 endprintln [triple, double].map(do (f) => f(10)) # [30, 20]Calling an instance of a struct without call raises an InvalidOperationError.
Static members
Section titled “Static members”static fn defines a method called on the struct itself. static @name = value defines a variable shared by every instance; methods reach it with @@name, and outside code with StructName.name.
struct Ticket static @issued = 0
fn new(holder) @@issued += 1 @number = @@issued @holder = holder end
static fn count() @@issued endend
Ticket.new("Ada")t = Ticket.new("Grace")
println t.number # 2println Ticket.count() # 2println Ticket.issued # 2
Ticket.issued = 0println Ticket.count() # 0A child struct shares its parent’s static variables: @@name in a parent method, and Child.name from outside, refer to the variable the parent declared. A child that declares its own static @name gets a separate one.
Inheritance
Section titled “Inheritance”struct Child < Parent inherits every method of Parent. A child can override a method by defining one with the same name. Kiwi has single inheritance.
struct Animal fn new(name) @name = name end
fn speak() "..." end
fn introduce() "${@name} says ${speak()}" endend
struct Dog < Animal fn speak() "Woof" endend
println Dog.new("Rex").introduce() # Rex says Woofoverride before fn marks a method as an intentional override. It is optional and currently informational.
Calling the parent with super
Section titled “Calling the parent with super”super.method(...) calls the parent struct’s version of a method on the current instance. It works for constructors and for any other method, and resolves relative to the struct that defined the method being run, so chains of several levels work as expected.
struct Animal fn new(name) @name = name end
fn speak() "..." endend
struct Dog < Animal fn speak() "Woof" endend
struct PoliceDog < Dog fn new(name, badge) super.new(name) @badge = badge end
fn speak() super.speak() + "! (badge #${@badge})" endend
println PoliceDog.new("Rex", 7).speak() # Woof! (badge #7)Abstract structs
Section titled “Abstract structs”An abstract struct can’t be instantiated; it exists to be inherited. Inside it, abstract fn name(params) end declares a method that every concrete child must implement. A child that misses one is rejected as soon as it is defined.
abstract struct Shape abstract fn area() end
fn describe() "${typeof(@)} with area ${area()}" endend
struct Square < Shape fn new(side) @side = side end
fn area() @side * @side endend
println Square.new(3).describe() # Square with area 9abstract struct Shape abstract fn area() endend
Shape.new() # AbstractInstantiationError: Cannot instantiate abstract struct `Shape`.abstract struct Shape abstract fn area() endend
struct Blob < Shapeend# AbstractMethodError: Struct `Blob` must implement abstract method `area`.Private methods
Section titled “Private methods”private fn marks a method as an internal helper that only the struct itself uses.
struct Password fn new(text) @hash = digest(text) end
fn matches(text) digest(text) == @hash end
private fn digest(text) crypto::sha256_hash(text) endend
p = Password.new("hunter2")println p.matches("hunter2") # trueCalling a private method from outside the struct raises an InvalidContextError. Methods of the struct, of structs that inherit from it, and lambdas created in those methods can call it:
struct Password fn new(text) @hash = digest(text) end
private fn digest(text) crypto::sha256_hash(text) endend
Password.new("hunter2").digest("x") # InvalidContextErrorMethods named like packages
Section titled “Methods named like packages”Inside a struct, a bare call such as path() reaches the struct’s own path method even though path is also a standard library package, since a package can’t be called. Qualified names still mean the package: time::ticks() works in a struct that has a time() method.
Working with instances
Section titled “Working with instances”| Method | Returns |
|---|---|
.is_a(Struct) | Whether the instance is that struct or inherits from it |
.type(), typeof(obj) | The struct’s name |
.keys() | The instance variable names |
.values() | The instance variable values |
.set(name, value) | Sets an instance variable by name |
.clone() | A new instance with the same instance variables |
struct User fn new(name, role) @name = name @role = role endend
u = User.new("Ada", "admin")println u.is_a(User) # trueprintln typeof(u) # Userprintln u.keys() # ["name", "role"]println u.values() # ["Ada", "admin"]
u.set("role", "owner")println u.role # owner.clone() copies the instance variables but not the lists or hashmaps they point to: a cloned instance shares those with the original.
Two instances are == when they are the same struct and their instance variables are equal.
Related pages
Section titled “Related pages”- Operator Overloading to give structs
+,==and other operators. - Decorators work on methods too.
hashableaddsto_hash()for converting instances to hashmaps.- Enums for named sets of constants.