breaking и break
break позволяет делать возвраты (в том числе глубокие). Это продвинутый аналог return, но возврат относится не к телу метода, а к определению breaking. break имеет некоторые дополнительные расходы ресурсов при работе, поэтому нужно использовать только там, где это оправдано.
Метод breaking принимает один аргумент - это код, который будет исполнен в контексте. Если код исполнен, то результатом breaking и будет результат кода:
def main = {
fix r = 123.breaking: { $ + 777}
println: r
}
Выдаёт:
900
Но код внутри breaking можно прервать досрочно при помощи break. break принимает один аргумент, который и станет результатом breaking в случае срабатывания этого break.
def main = {
0..3 as v.${
fix r = breaking: {
if (v == 0)
break: "zero"
if (v == 1)
break: "one"
"???"
}
println: s<<#{v}: #{r}>>
}
}
Выдаёт:
0: zero
1: one
2: ???
3: ???
break, как видно, может использоваться в нескольких местах, относящихся к одному breaking.
Тип результата breaking является общим типом для прямого результата (без срабатывания break) и всех типов результатов, переданных при помощи break:
def main = {
0..3 as v.${
fix r = breaking: {
if (v == 0)
break: "abc"
if (v == 1)
break: unit
3.14
}
println: s<<#{v}: #{r} (#{type(r)})>>
}
}
Выдаёт:
0: abc ((libretto/Real | libretto/String | libretto/Unit))
1: unit ((libretto/Real | libretto/String | libretto/Unit))
2: 3.14 ((libretto/Real | libretto/String | libretto/Unit))
3: 3.14 ((libretto/Real | libretto/String | libretto/Unit))
Здесь результатом breaking является объединение Real (прямой результат), Int и Unit (результаты break).
break может располагаться в области видимости breaking, но физически исполняться в другом месте:
def test(code: Func[()]) = {
code.!
123
}
def main = {
0..3 as v.${
fix r = breaking: {
test: #{ if (v == 1) break: "Hello" }
}
println: s<<#{v}: #{r}>>
}
}
Выдаёт:
0: 123
1: Hello
2: 123
3: 123
break в этом примере располагается в области видимости breaking, но “физически” срабатывает внутри метода test. Это позволяет делать глубокие возвраты значения.
Вложенные breaking поддерживаются, но break относится к самому внутреннему breaking:
def main = {
fix r = breaking: {
breaking: {
break: "Hello!"
}
123
}
println: r
}
Выдаёт:
123
Если вынести “физический” момент срабатывания break за пределы breaking, то будет исключение во время исполнения:
def main = {
fix r = breaking: {
func: #{ break: 123; () }
}
r.*case x: Func[()] => x.!
}
Выдаётся исключение Uncaught break 123 (breakId 1).
Если breaking заведомо заканчивается только вызовом break, то всё равно нужно учитывать тип блока для breaking.
Пример (поиск последовательным перебором целого числа, которое больше 0 и которое без остатка делится на 3, 5, и 7 одновременно):
def main = {
var c = 1
fix r = breaking: {
while (unit): {
if (c mod 3 == 0 and c mod 5 == 0 and c mod 7 == 0) break: c
c = c + 1
}
}
println: r
println: type(r)
}
Выдаёт
105
libretto/Int?
Здесь вечный цикл while(unit), который прерывается только break, но компилятор в настоящее время не определяет это, предполагая завершение while. Поскольку у while тип результата пустота, то у блока для breaking тоже тип пустота. Поскольку break возвращает результат Int, то обобщение с пустотой даёт Int? - это и есть тип результата breaking.
Поэтому в таком случае нужно в блок для breaking добавить значение с типом - (наиболее специфичный тип Libretto). Обобщение с этим типом не меняет тип. Таким значением может быть, например, вызов error.
Подправленная версия:
def main = {
var c = 1
fix r = breaking: {
while (unit): {
if (c mod 3 == 0 and c mod 5 == 0 and c mod 7 == 0) break: c
c = c + 1
}
error("Unreachable code")
}
println: r
println: type(r)
}
Выдаёт:
105
libretto/Int