a fun language for serious programming

Constructors

Constructors in Nit behave differently.

Their objective is double :

  • be compatible with full multiple-inheritance
  • be simple enough to be KISS and compatible with the principle of least surprise.

new construction and simple classes

Classes in OO models are often a simple aggregates of attributes and methods.

By default, the new construction requires a value for each attribute defined in a class without a default value.

class Product
    var id: String
    var description: String
    var price: Float
end
var p = new Product("ABC", "Bla bla", 15.95)
assert p.id == "ABC"

In subclasses, additional attributes are automatically collected.

class Product
    var id: String
    var description: String
    var price: Float
end
class Book
    super Product
    var author: String
end

var book = new Book("ABC", "Bla bla", 15.95, "John Doe")

special init method

The special init method is automatically invoked after the end of a new construction. It is used to perform additional systematic tasks.

Because the init is run at the end of the initialization sequence, initialized attributes are usable in the body.

class Product
    var id: String
    var description: String
    var price: Float
end
class OverpricedProduct
    super Product
    init
    do
        price = price * 10.0
    end
end
var op = new OverpricedProduct("ABC", "Bla bla", 15.95)
assert op.price.is_approx(159.50, 0.001)

Uncollected attributes

There are three cases for which an attribute is not collected in a new construction.

  • Attributes with a default value
  • Attributes with the annotation noinit
  • Attributes introduced in refinement of classes
class Product
    var id: String
    var description: String
    var price: Float
end
class TaxedProduct
    super Product
    var tax_rate = 9.90
    var total_price: Float is noinit
    init
    do
        total_price = price * (1.0 + tax_rate100.0)
    end
end
var tp = new TaxedProduct("ABC", "Bla bla", 15.95)
assert tp.total_price.is_approx(17.52905, 0.00001)

Note: The orchestration here is important. In order, the following is executed:

  1. All defauts values are computed and set
  2. Setters are invoked.
  3. init is invoked.

Therefore, total_price cannot be initialised with a default value, because at the time of the computation of the default values, the attribute price in not yet initialised.

Generalized initializers

Initializers are methods that are automatically invoked by new. In fact, by default, the setter of an attribute is used as an initializer.

autoinit is used to register a method as a setter.

class Product
    var id: String
    var description: String
    var price: Float
end
class FooProduct
    super Product
    fun set_xy(x, y: Int) is autoinit do z = x * 10 + y
    var z: Int is noinit
end
var fp = new FooProduct("ABC", "Bla bla", 15.96, 1, 3)
assert fp.z == 13

Generalized setters are a powerful tool, but only needed in rare specific cases. In most cases, there is no reason for an argument of a new construction to not be stored in the object as a real attribute.

Inheritance

As explained above, one of the main advantage of these constructors is their compatibility with multiple inheritance.

class Product
    var id: String
    var description: String
    var price: Float
end
class OverpricedProduct
    super Product
    init
    do
        price = price * 10.0
    end
end
class TaxedProduct
    super Product
    var tax_rate = 9.90
    var total_price: Float is noinit
    init
    do
        total_price = price * (1.0 + tax_rate100.0)
    end
end
class FooProduct
    super Product
    fun set_xy(x, y: Int) is autoinit do z = x * 10 + y
    var z: Int is noinit
end
class MultiProduct
    super OverpricedProduct
    super TaxedProduct
    super FooProduct
end
var mp = new MultiProduct("ABC", "Bla bla", 15.96, 1, 3)
assert mp.id == "ABC"
assert mp.price.is_approx(159.6, 0.001)
assert mp.total_price.is_approx(175.4, 0.001)
assert mp.z == 13

Named init

Named init are less flexible trough inheritance, thus should no be used. They allow to have additional constructor for classes and more control in the construction mechanism.

class Point
    var x: Float
    var y: Float

    init origin
    do
        init(0.0, 0.0)
    end

    init polar(r, phi: Float)
    do
        var x = r * phi.cos
        var y = r * phi.sin
        init(x, y)
    end

    redef fun to_s do return "({x},{y})"
end
var p1 = new Point(1.0, 2.0)
assert p1.to_s ==  "(1.0,2.0)"
var p2 = new Point.origin
assert p2.to_s ==  "(0.0,0.0)"
var p3 = new Point.polar(1.0, 2.0)
assert p3.to_s ==  "(-0.416,0.909)"

Legacy init

nameless init defined with argument or with an explicit visibility are still accepted as a fallback of the old-constructors. They should not be used since they will be removed in a near future.

new factories

new factories allow to completely shortcut the class instantiation mechanism. It could be used to provide new syntax on non-concrete class (mainly extern class).

new factories behave like a top-level function that return the result of the construction. It is basically some kind of syntactic sugar.

abstract class Person
    var age: Int
    new(age: Int)
    do
        if age >= 18 then
            return new Adult(age)
        else
            return new Child(age)
        end
    end
end
class Adult
    super Person
    # ...
end
class Child
    super Person
    # ...
end