OxL Справочник OxL
РусскийEnglish

V.3 Конструкторы, интерфейсы, встраивание, операторы

oxl
// Деньги храним в копейках. @repr(i64): по сети и в БД Money выглядит как одно число.
// @derive генерирует копирование, сравнение, хеш и вывод в строку.
@repr(i64) @derive(Copy, Eq, Ord, Hash, Show)
pub(api) data Money { minor: i64 }

impl Money {
  pub const ZERO: Money = Money{ minor: 0 }                   // ассоциированная константа
  static fn rub(r: i64) -> Money => Money{ minor: r * 100 }   // static: вызывается как Money.rub(…)
  static fn zero() -> Money => Money.ZERO
  // Перегрузка операторов: теперь можно писать 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 }
}

// Как превращать Money в i64 и обратно при сериализации (§9.5)
impl Repr<i64> for Money {
  fn toRepr() -> i64 => minor
  static fn fromRepr(v: i64) -> Money | DecodeError => Money{ minor: v }
}

// Интерфейс структурный: подходит любой тип с методом price()
interface Priced { fn price() -> Money }

data Product { sku: Sku; title: str; base: Money }
impl Product {
  open fn price() -> Money => base    // open — метод можно переопределить во встраивающем типе
}

type Percent = u8 where 0..=90        // уточнённый тип: значение проверяется при создании

// @sealed: литерал Discounted{…} снаружи запрещён, создать можно только через init
@sealed data Discounted {
  embed Product                       // встраивание: поля и методы Product доступны напрямую
  percent: Percent
}
impl Discounted {
  // Именованный конструктор. Percent.from проверяет диапазон и может вернуть InvalidValue.
  init of(p: Product, percent: u8) -> Self | InvalidValue =>
    Self{ Product: p, percent: Percent.from(percent)? }

  // override обязателен; super.price() — версия из Product
  override fn price() -> Money => super.price() - super.price() * (percent as i64) / 100
}

// [dyn Priced] — срез значений разных типов, вызов price() через vtable
fn total(items: [dyn Priced]) -> Money pure =>
  items.fold(Money.ZERO, (acc, i) => acc + i.price())

let d = Discounted.of(book, 15)?      // ? пробрасывает InvalidValue, если процент вне 0..=90
assert d.price() == Money.rub(850)    // при book.base = Money.rub(1000)

Discounted удовлетворяет Priced через продвинутый и переопределённый price. Литерал Discounted{ … } вне impl запрещён (@sealed), поэтому процент всегда проверен.

Исходник: docs/spec/oxl-standard.md