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

Замыкания (трейты, анонимные и локальные функции)

Замыкания

Метод трейта, анонимная функция, локальная функция могут служить замыканием для доступа к сущностями, которые определены в другой области видимости.

При помощи замыкания возможен доступ ко всем переменным (fix, var, as, case), параметрам функции/метода (кроме this), локальным функциям.

def call(f: Func[Int, Unit]) = {
  f.!(100)
}
def test(param: Int) = {
  fix a = 777
  
  def local(v: Int) = 
    println: "local: " + (param + v + a) // замыкание с param и a
  fix f = func: #(v: Int){ 
    println: "func: " + (param + v + a) // замыкание с param и a
  }

  local(100)
  call(f)
}

def main = {
  test(123)
}

Выдаёт:

local: 1000
func: 1000

Замыкание с локальной функцией:

def main = {
  def inc(v: Int) = v + 1
  fix f = func: #(i: Int){ println: inc(i) } // замыкание с inc
  println("Before")
  f.!(100)
}

Выдаёт:

Before
101

this в замыкании

В замыкании использовать this метода, в котором производится определение, напрямую невозможно. В теле анонимной функции или локальной функции доступ к this запрещен компилятором. В теле метода трейта this разрешен, но обозначает сам экземпляр трейта, а не this обрамляющего метода.

Поэтому для доступа к this в замыкании нужно значение получать через промежуточную fix переменную.

def Int createSumFunc: Func[Int, Int] = {
  fix ctx = this // подготовка для замыкания
  func: #(a: Int){ ctx + a } // замыкание с ctx
}

def main = {
  fix sumFunc = 123.createSumFunc
  println: sumFunc.!(777)
}

Выдаёт:

900

Особенность замыкания с var

var-переменная даёт доступ замыканию не до самого значения, а до контейнера, в котором производится накопление, изменение значения. Поэтому значения в этом контейнере могут отличаться между моментом создания (определения) замыкания и моментом его запуска (использования):

def main = {
  var i = 0
  var funcs: Func[Unit]* = ()
  while(i < 4): {
    funcs += func: #{ println(i) } // определение
    i = i + 1
  }
  println("After while")
  funcs.${ $.! } // использование
}

Выдаёт:

After while
4
4
4
4

Все созданные анонимные функции, которые используют var i, вызываются после цикла while, когда значение i (т.е. значение в контейнере, который стоит за именем i) будет уже 4.

А вот замыкание с неизменяемыми переменными (fix, as, case, параметры метода или функции) получает именно значения на момент замыкания.

def main = {
  fix funcs: Func[Unit]* = 0..3 as i.
    func: #{ println(i) } // определение
  println("After path")
  funcs.${ $.! } // использование
}

Выдаёт:

After path
0
1
2
3

Для обхода проблемы var нужно использовать fix переменную, в которую помещать текущее (на момент определения) значение var-переменной:

def main = {
  var i = 0
  var funcs: Func[Unit]* = ()
  while(i < 4): {
    fix currentI = i // именно текущее значение
    funcs += func: #{ println(currentI) } // определение
    i = i + 1
  }
  println("After while")
  funcs.${ $.! } // использование
}

Выдаёт:

After while
0
1
2
3

Из-за такой особенности замыкание с изменяемыми переменными запрещено в некоторых языках. Но в Libretto эта проблема меньше выражена, поэтому замыкание обеспечивает доступ к var. И это позволяет организовать хранение состояний объектов (экземпляров трейтов).

Замыкание с var как состоянием объекта

В Libretto нет объектов или классов (в прямом виде). А структуры являются неизменяемыми. Именно var переменные позволяют организовать состояния изменяемых объектов (экземпляров трейтов) через замыкание:

trait Counter {
  def inc: ()
  def `!`: Int
}

def counter(start: Int) = {
  var value = start
  new Counter {
    def inc = {
      value = value + 1 // работа с var, а не с полем
    }
    def `!` = value // работа с var, а не с полем
  }
}

def main = {
  fix counter = counter(100) // создание объекта
  println("value: " + counter.!)
  println("value: " + counter.!)
  counter.inc
  println("after inc: " + counter.!)
  counter.inc
  counter.inc
  println("after inc and inc: " + counter.!)
}

Выдаёт:

value: 100
value: 100
after inc: 101
after inc and inc: 103

todo трейты

Замыкание с var для накопления результата

Замыкание с var переменной может использоваться для накопления результата:

def nTimes(n: Int, f: Func[Any*]) = {
  1..n.${ f.! } // использование
  ()
}

def main = {
  var c = 0
  nTimes(10): #{ c = c + 10 } // определение
  println(c)
}

Выдаёт:

100

В том числе накопление последовательности:

def range(from: Int, to: Int, f: Func[Int, Any*]) = {
  from..to as i .${ f.!(i) } // использование
  ()
}

def main = {
  var buffer: Int*
  range(10, 19): #(v){ buffer += v } // определение
  println: buffer
}

Выдаёт:

(10,11,12,13,14,15,16,17,18,19)

todo см. буфер