Classes and traits
Instance members are accessed with
<object>.<variable/method>
.Automatically added methods (
__init__()
,__str__()
, …) are only added if missing.Traits cannot have members.
Traits cannot be instantiated, only classes can.
Implemented trait methods must be decorated with
@trait(T)
or@trait(T, <method>)
.A class can implement zero or more traits.
Destructors may not raise exceptions.
Special methods
Name |
Usage |
Comment |
---|---|---|
|
|
Constructor. |
|
|
String representation, often for debugging. |
|
|
Equal to other object. |
|
|
Less than other object. |
|
|
Hash. |
Example 1
A class with a member value
and a method inc()
.
The constructor func __init__(self, value: i64)
(and more methods)
are automatically added to the class as they are missing.
class Foo:
value: i64
func inc(self):
self.value += 1
func main():
print("f1:")
f1 = Foo(0)
print(f1)
f1.inc()
print(f1)
print("f2:")
f2 = Foo(5)
print(f2)
❯ mys run
✔ Reading package configuration (0 seconds)
✔ Building (0.01 seconds)
f1:
Foo(value=0)
Foo(value=1)
f2:
Foo(value=5)
Example 2
An example of how to use traits.
trait Base:
func add(self, value: i64) -> i64:
pass
func surprise(self, value: i64) -> i64:
# Default implementation.
return value * value
class Foo(Base):
@trait(Base)
func add(self, value: i64) -> i64:
return value + 5
func mul(self, value: i64) -> i64:
return value * 3
class Bar(Base):
@trait(Base)
func add(self, value: i64) -> i64:
return value + 10
@trait(Base)
func surprise(self, value: i64) -> i64:
return value * value * value
func div(self, value: i64) -> i64:
return value / 3
func calc(base: Base, value: i64):
print(f"base.add({value}):", base.add(value))
print(f"base.surprise({value}):", base.surprise(value))
match base:
case Foo() as foo:
print(f"foo.mul({value}):", foo.mul(value))
case Bar() as bar:
print(f"bar.div({value}):", bar.div(value))
func main():
value = 12
calc(Foo(), value)
calc(Bar(), value)
❯ mys run
✔ Reading package configuration (0 seconds)
✔ Building (0.01 seconds)
base.add(12): 17
base.surprise(12): 144
foo.mul(12): 36
base.add(12): 22
base.surprise(12): 1728
bar.div(12): 4
Example 3
A class that implements two traits where both traits has the method
work()
. One of the two must be renamed in the implementing class.
trait Base1:
func work(self):
pass
trait Base2:
func work(self):
pass
class Foo(Base1, Base2):
@trait(Base1)
func work(self):
print("work()")
# Must rename due to name clash.
@trait(Base2, work)
func work_2(self):
print("work_2()")
func base_1_work(base: Base1):
base.work()
func base_2_work(base: Base2):
# Calls Foo's work_2() method.
base.work()
func main():
foo = Foo()
foo.work()
foo.work_2()
base_1_work(foo)
base_2_work(foo)
❯ mys run
✔ Reading package configuration (0 seconds)
✔ Building (0.01 seconds)
work()
work_2()
work()
work_2()
Example 4
Make the implemented trait method private by renaming it in the implementing class.
trait Base:
func work(self):
pass
class Foo(Base):
@trait(Base, work)
func _work(self):
print("_work()")
func work(base: Base):
base.work()
func main():
foo = Foo()
# Cannot call foo.work() as that method does not exist on the class.
work(foo)
❯ mys run
✔ Reading package configuration (0 seconds)
✔ Building (0.01 seconds)
_work()
Example 5
The class has a method that name clashes with a trait method. Rename implemented trait method in the class.
trait Base:
func work(self):
pass
class Foo(Base):
func work(self):
print("work()")
@trait(Base, work)
func work_2(self):
print("work_2()")
func work(base: Base):
base.work()
func main():
foo = Foo()
foo.work()
foo.work_2()
work(foo)
❯ mys run
✔ Reading package configuration (0 seconds)
✔ Building (0.01 seconds)
work()
work_2()
work_2()
Example 6
Trait methods can call methods in the same trait, any functions and classes and use global variables.
func age() -> i64:
return 5
trait Formatter:
func format(self) -> string:
# Calling method name() and function age().
return f"Name: {self.name()}, Age: {age()}"
func name(self) -> string:
pass
class Foo(Formatter):
func name(self) -> string:
return "Bob"
func main():
foo = Foo()
print(foo.format())
❯ mys run
✔ Reading package configuration (0 seconds)
✔ Building (0.01 seconds)
Name: Bob, Age: 5
Ideas
Ideas on how to implement traits and classes to remove Object base class limitation. It is problematic when a class implements multiple traits, at least when all traits inherits from it.
Example 3
class Base1 {
public:
virtual void Base1_work() = 0;
virtual String Base1___str__() = 0;
};
class Base2 {
public:
virtual void Base2_work() = 0;
virtual String Base2___str__() = 0;
};
class Foo : public Base1, public Base2 {
public:
void Base1_work() override;
void Base2_work() override;
String Base1___str__() override;
String Base2___str__() override;
String __str__();
};
void Foo::Base1_work()
{
std::cout << "work()" << "\n";
}
void Foo::Base2_work()
{
std::cout << "work_2()" << "\n";
}
String Foo::Base1___str__()
{
return __str__();
}
String Foo::Base2___str__()
{
return __str__();
}
String Foo::__str__()
{
return "Foo()";
}
Example 6
i64 age()
{
return 5;
}
class Formatter {
public:
virtual String Formatter_format();
virtual String Formatter_name() = 0;
virtual String Formatter___str__() = 0;
};
String Formatter::Formatter_format()
{
return String("Name: ") + name() + String(", Age: ") + age();
}
class Foo : public Formatter {
public:
String Formatter_name() override;
String Formatter___str__();
String __str__();
};
String Foo::Formatter_name()
{
return String("Bob");
}
String Foo::Formatter___str__() override
{
return __str__();
}
String Foo::__str__()
{
return "Foo()";
}