Замыкания (трейты, анонимные и локальные функции)
Замыкания
Метод трейта, анонимная функция, локальная функция могут служить замыканием для доступа к сущностями, которые определены в другой области видимости.
При помощи замыкания возможен доступ ко всем переменным (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 см. буфер