6.8. kink/CONTROL¶
このモジュールは、基本的な制御構造をいくつか提供する。
6.8.1. CONTROL.while($continue? $body)¶
whileは、$continue?がtrueを戻すかぎり、$bodyをくりかえし呼び出す。
whileはまず$continue?を引数なしで呼び出す。$continue?がtrueを戻せば、whileは$bodyを引数なしで呼び出し、最初に戻る。$continue?がfalseを戻せば、whileは戻る。
事前条件
$continue?はboolを戻すサンクでなければならない。
$bodyはサンクでなければならない。
例
:CONTROL.require_from('kink/')
:Vec <- [1 2 3]
CONTROL.while{ Vec.any? }{
:N = Vec.pop_front
stdout.print_line(N.repr)
}
# Output:
# 1
# 2
# 3
6.8.2. CONTROL.try($body $returned_cont $raised_cont)¶
tryは引数なしで$bodyを呼び出す。
$bodyの呼び出しが戻ったら、tryは、$bodyの戻り値を引数として、$returned_contを末尾呼び出しする。
$bodyの呼び出しが例外を投げたら、tryは、exceptionを引数として、$raised_contを末尾呼び出しする。
事前条件
$bodyはサンクでなければならない。
$returned_contはひとつの引数を取る関数でなければならない。
$raised_contはexceptionを取る関数でなければならない。
例
:TRACE.require_from('kink/')
:CONTROL.require_from('kink/')
:attempt_divide_10_by <- {(:Divisor)
CONTROL.try(
{ 10 // Divisor }
{(:Result)
stdout.print_line('result: {}'.format(Result))
}
{(:Exc)
Exc.desc_iter.each{(:Line)
stdout.print_line(Line)
}
}
)
}
attempt_divide_10_by(2) # => result: 5
attempt_divide_10_by(0)
# Output:
# -- main exception
# [..root..]
# ,,,
# [..kont tag..]
# [builtin:kink-mods/kink/CONTROL.kn L94 C10 body] :R = -->body
# {(stdin) L6 C10 op_intdiv} { 10 -->// Divisor }
# Num.op_intdiv(Divisor): zero division: 10 is divided by 0
6.8.3. CONTROL.with_return($body)¶
with_returnは、関数$returnを引数として$bodyを呼び出す。$bodyの呼び出しの中で、サンク$exit_contを引数として$returnを呼び出すことができる。$returnが呼び出されると、$bodyの呼び出しはそこで中断し、with_returnは$exit_contを末尾呼び出しする。
$bodyの呼び出しが、$returnを呼ぶことなしに最後まで到達したら、with_returnは例外を投げる。
事前条件
$bodyは関数$returnを取る関数でなければならない。
例
:CONTROL.require_from('kink/')
:gcd <- {(:X :Y)
CONTROL.with_return{(:return)
:R = X % Y
if(R == 0){
return{ Y }
}
return{ gcd(Y R) }
}
}
stdout.print_line(gcd(1071 462).repr) # => 21
gcdの再帰的な呼び出しはスタックを消費しない。なぜなら、gcdはwith_returnの末尾文脈で呼び出されており、これはgcdの末尾文脈でもあるからだ。つまり、これは末尾再帰である。
なおgcdは、with_returnを使わず、より単純に実装できる。
:gcd <- {(:X :Y)
:R = X % Y
if(R == 0
{ Y }
{ gcd(Y R) }
)
}
stdout.print_line(gcd(1071 462).repr) # => 21
例: ネストした呼び出し
:CONTROL.require_from('kink/')
:R <- CONTROL.with_return{(:return)
CONTROL.with_return{(:inner_return)
return{ 42 }
_does_not_reach_here_
}
_does_not_reach_here_
}
stdout.print_line(R.repr) # => 42
6.8.4. CONTROL.with_finally($body)¶
with_finallyは、関数$finallyを引数として$bodyを呼び出す。$bodyの呼び出しの中で、サンク$cleanupを引数として$finallyを任意回数呼び出すことができる。$cleanupは、次の状況で呼び出されることが保証される。
(1) $bodyの呼び出しが戻ったとき
(2) $bodyの呼び出しからCONTROL.with_returnの$returnによって脱出したとき
(3) $bodyが例外を投げたとき
$finallyがn回、サンク $cleanup_1, $cleanup_2 ,,, $cleanup_n を、この順番に引数として呼ばれたとき、これらのサンクは逆順で呼ばれることが保証される: $cleanup_n ,,, $cleanup_2, $cleanup_1
$cleanupの呼び出しを、CONTROL.with_returnで脱出してはならない。
結果
(1) $bodyの呼び出しが戻ったとき
どのcleanupサンクも例外を投げなければ、with_finallyは$bodyの呼び出しが戻した値を戻す。
ひとつ以上のcleanupサンクが例外を投げれば、それらの例外はひとつの例外としてチェーンされて、with_finallyはそのチェーンされた例外を投げる。
(2) $bodyの呼び出しからCONTROL.with_returnの$returnによって脱出したとき
どのcleanupサンクも例外を投げなければ、with_finallyは脱出を再開する。
ひとつ以上のcleanupサンクが例外を投げれば、それらの例外はひとつの例外としてチェーンされて、with_finallyはそのチェーンされた例外を投げる。
(3) $bodyが例外を投げたとき
どのcleanupサンクも例外を投げなければ、with_finallyは$bodyが投げた例外を投げる。
ひとつ以上のcleanupサンクが例外を投げれば、それら例外は$bodyが投げた例外のチェーンに追加されて、with_finallyはそのチェーンされた例外を投げる。
事前条件
$bodyは、関数$finallyを取る関数でなければならない。
例
:CONTROL.require_from('kink/')
:FILE.require_from('kink/io/')
# copy /etc/os-release to ./os-release.copy
CONTROL.with_finally{(:finally)
:In = FILE.open_to_read('/etc/os-release')
finally{ In.close }
:Out = FILE.open_to_write('os-release.copy')
finally{ Out.close }
Out.write(In.read_all)
}
6.8.5. CONTROL.with_return_finally($body)¶
with_return_finallyは、$returnと$finallyを引数として$bodyを呼び出す。
これは、with_returnとwith_finallyを、次のようにネストするのと同等である:
CONTROL.with_return{(:return)
CONTROL.with_finally{(:finally)
body($return $finally)
}
}
$returnは$bodyの呼び出しをその場で中断する。with_return_finallyは、$returnに渡されたサンクを末尾呼び出しする。
$finallyは、片付けサンクを登録する。片付けの意味論の詳細は、with_finallyを見よう。
事前条件
$bodyは、関数$returnと$finallyを取る関数でなければならない。
例
:CONTROL.require_from('kink/')
:CHARSET.require_from('kink/')
:FILE.require_from('kink/io/')
:scan_file <- {(:Fname)
CONTROL.with_return_finally{(:return :finally)
:S = FILE.open_to_scan(Fname CHARSET.utf8){(:C)
C.on_error{ return{ 'no such file' } }
}
finally{ S.close }
:Text = S.scan_all
return{ Text }
}
}
stdout.print_line(scan_file('/etc/locale.conf')) # => LANG=en_US.UTF-8
stdout.print_line(scan_file('/no/such/file')) # => no such file