V.3 Constructors, interfaces, embedding, operators
// Money is stored in kopecks. @repr(i64): over the network and in the DB Money looks like a single number.
// @derive generates copying, comparison, hash and string output.
@repr(i64) @derive(Copy, Eq, Ord, Hash, Show)
pub(api) data Money { minor: i64 }
impl Money {
pub const ZERO: Money = Money{ minor: 0 } // an associated constant
static fn rub(r: i64) -> Money => Money{ minor: r * 100 } // static: called as Money.rub(…)
static fn zero() -> Money => Money.ZERO
// Operator overloading: now we can write a + b, a * 3
static op +(a: Money, b: Money) -> Money => Money{ minor: a.minor + b.minor }
static op -(a: Money, b: Money) -> Money => Money{ minor: a.minor - b.minor }
static op *(a: Money, k: i64) -> Money => Money{ minor: a.minor * k }
static op /(a: Money, k: i64) -> Money => Money{ minor: a.minor / k }
}
// How to turn Money into i64 and back during serialisation (§9.5)
impl Repr<i64> for Money {
fn toRepr() -> i64 => minor
static fn fromRepr(v: i64) -> Money | DecodeError => Money{ minor: v }
}
// The interface is structural: any type with a method price() fits
interface Priced { fn price() -> Money }
data Product { sku: Sku; title: str; base: Money }
impl Product {
open fn price() -> Money => base // open — the method can be overridden in an embedding type
}
type Percent = u8 where 0..=90 // a refined type: the value is checked on creation
// @sealed: the literal Discounted{…} is forbidden outside, it can only be created through init
@sealed data Discounted {
embed Product // embedding: the fields and methods of Product are directly accessible
percent: Percent
}
impl Discounted {
// A named constructor. Percent.from checks the range and may return InvalidValue.
init of(p: Product, percent: u8) -> Self | InvalidValue =>
Self{ Product: p, percent: Percent.from(percent)? }
// override is mandatory; super.price() is the version from Product
override fn price() -> Money => super.price() - super.price() * (percent as i64) / 100
}
// [dyn Priced] is a slice of values of different types, price() is called through a vtable
fn total(items: [dyn Priced]) -> Money pure =>
items.fold(Money.ZERO, (acc, i) => acc + i.price())
let d = Discounted.of(book, 15)? // ? propagates InvalidValue if the percentage is outside 0..=90
assert d.price() == Money.rub(850) // with book.base = Money.rub(1000)Discounted satisfies Priced through the promoted and overridden price. The literal Discounted{ … } is forbidden outside impl (@sealed), so the percentage is always checked.
Source: docs/spec/oxl-standard.en.md