Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Специальные методы (string, equalityKey, !, =)

Метод string

Метод string используется для строкового представления сущности, в контексте которой этот метод определен.

Это может быть структура или трейт. Метод может быть определен внутри структуры или трейта. Или как глобальный метод.

Метод не должен принимать параметры.

struct Test(string: String)

struct Value(value: Int)
def Value string = "Value: "+ this.value

trait Calc {
  def string: String
}

trait Work {
}

def Work string = "Work string"

def main = {
  println: Test("Test string")
  println: Value(123)
  println: new Calc {
    def string = "Calc string"
  }
  println: new Work {}
}

Выдаёт:

Test string
Value: 123
Calc string
Work string

Тип результата метода string может быть любой, не обязательно String. И может быть любая кардинальность:

struct Test(string: Int*)

struct Value(value: Int*)
def Value string = this.value

trait Calc {
  def string: Int*
}

trait Work {
}

def Work string = (123, 123)

def main = {
  println: Test((123, 123))
  println: Value((123, 123))
  println: new Calc {
    def string = (123, 123)
  }
  println: new Work {}
}

Выдаёт:

(123,123)
(123,123)
(123,123)
(123,123)

Но конечное преобразование в String в таком случае делает компилятор. Для предсказуемого поведения лучше определять метод string с типом результата String.

Специальный метод string ищется только среди полностью определенных (не шаблонных) методов, у которых в контексте явно или через объединение указан соответствующий трейт или структура. Методы string без контекста, с контекстом Any, шаблоны не используются для генерации строкового представления.

Метод equalityKey

Метод equalityKey используется только для структуры, он определяет способ сравнение экземпляров структур. См. соответствующий раздел в описании структур.

Специальный метод equalityKey ищется только среди полностью определенных (не шаблонных) методов, у которых в контексте явно или через объединение указана соответствующая структура. Методы equalityKey без контекста, с контекстом Any, шаблоны не используются для управления сравнением.

Для трейтов этот метод не поддерживается.

Метод !

При помощи обратных кавычек `` можно определять методы с именами, которые не допускает парсер напрямую. Например, можно определить и вызвать метод ! как `!`:

def `!` = "test"

def main = {
  println: `!`
}

Выдаёт:

test

Но особенность метода ! в том, что его нужно определять при помощи обратных кавычек ``, но вызывать можно без них:

def `!` = "test"

def main = {
  println: !
}

Выдаёт:

test

Поскольку это обычный метод, то нет ограничений на число параметров, контекст или тип результат.

В Libretto имя ! имеет смысл apply (применить). Например, в этом смысле он используется в анонимных функциях и отображениях.

Методы для ! и =

Например, в отображениях метод ! с одним параметром служит для получения значения, привязанного к указанному ключу. А для привязки используется комбинация ! и знака равно =:

def main = {
  fix m = map[Int, String]()
  m.!(1) = "one"
  println: m.!(1)
}

Выдаёт:

one

Вызов m.!(1) = "one" преобразуется в m.`!=`(1, "one").

struct Test(v: Int)

def Test `!=`(a: Int, b: Int) = a + b + this.v

def main = {
  fix t = Test(123)
  println: t.!(777) = 100
}

Выдаёт:

1000

Если параметров для ! нет, то вызова преобразуется в != с одним параметром:

struct Test(v: Int)

def Test `!=`(a: Int) = a + this.v

def main = {
  fix t = Test(123)
  println: t.! = 777
}

Выдаёт:

900

Это работает не только для =, но и для += и .=. И для любого числа параметров. Общее правило: вызов превращается в !=, !+=, !.=, в аргументы этого вызова сперва добавляются аргументы, что указаны для ! (если они есть), а последним аргументом добавляется выражение после =:

  • a.! = x преобразуется в a.`!=`(x)
  • a.! += x преобразуется в a.`!+=`(x)
  • a.! .= x преобразуется в a.`!.=`(x)
  • a.!(b) = x преобразуется в a.`!=`(b, x)
  • a.!(b) += x преобразуется в a.`!+=`(b, x)
  • a.!(b) .= x преобразуется в a.`!.=`(b, x)
  • a.!(b, c) = x преобразуется в a.`!=`(b, c, x)
  • a.!(b, c) += x преобразуется в a.`!+=`(b, c, x)
  • a.!(b, c) .= x преобразуется в a.`!.=`(b, c, x)
  • и т.д.

При определении такого метода с = в названии его нужно оборачивать в обратные кавычки ``

def Int `!=`(a: Int, b: Int, c: Int, d: Int, e: Int) =
  this + a + b + c + d + e 
def main = {
  println: 123.!(1,2,3,4) = 5
}

Выдаёт:

138

Например, простой буфер целых чисел:

trait IntBuffer {
  def `!`: Int*
  def `!=`(v: Int*): ()
  def `!.=`(v: Int*): ()
  def `!+=`(v: Int*): ()
}

def intBuffer(initValue: Int*) = {
  var buf: Int* = initValue
  new IntBuffer {
    def `!` = buf
    def `!=`(v) = buf = v
    def `!.=`(v) = buf .= v
    def `!+=`(v) = buf += v
  }  
}

def main = {
  fix b = intBuffer: (1, 2, 3)
  println: b.!
  b.! .= 0
  println: b.!
  b.! += (4, 5)
  println: b.!
  b.! = 6..9
  println: b.!
}

Выдаёт

(1,2,3)
(0,1,2,3)
(0,1,2,3,4,5)
(6,7,8,9)

Методы для = (общее правило)

Правило для ! и = - это частный случай более общего правила вызова методов с =.

Если присваивание (=, +=, .=) не связано с переменной (в левой части), то оно при компиляции заменяется на вызов метода:

  1. Имя метода (простое или с префиксом) формируется добавлением =, +=, .= к локальной части имени, стоящего последним шагом слева от присваивания.

    a = … превращается в `a=`

    a/b += … превращается в a/`b+=`

  2. В аргументы вызова сперва переходят аргументы в скобках слева от = (если такие есть), а правая часть от присваивания добавляется последним аргументом.

    a = 123 превращается в `a=`(123)

    a/b(c, d) += x превращается в a/`b+=`(c, d, x)

При определении такого метода с = в названии его нужно оборачивать в обратные кавычки ``

Такой подход можно использовать для имитации т.н. getter и setter. В качестве getter будут выступать обычные методы без параметров, а в качестве setter как раз метод с =.

def Test() = {
  var aValue = 0
  new {
    def `a=`(v: Int): () = aValue = v
    def a: Int = aValue
  }
}

def main = {
  fix obj = Test()
  obj.a = 123
  println: obj.a
  obj.a = obj.a + 777
  println: obj.a
}

Выдаёт:

123
900

В этом примере трейт Test выглядит как имеющий изменяемое поле a, но работа с ним - это вызов двух методов: a для чтения и `a=` для записи.