OxL Reference

V.3 Constructors, interfaces, embedding, operators

oxl
// 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