5.5. Control structure¶
5.5.1. Branches¶
One way branch can be done by if
preloaded function.
Example:
:print_if_positive <- {(:Num)
if(Num > 0){
stdout.print_line(Num.repr)
}
}
print_if_positive(-1) # => (no output)
print_if_positive(0) # => (no output)
print_if_positive(1) # => 1
If you want to raise an exception based on the condition,
you can use ||
or &&
instead of if
.
:NUM.require_from('kink/')
:fractional_part <- {(:N)
NUM.is?(N) || raise('N must be a num, but was {}'.format(N.repr))
N % 1
}
stdout.print_line(fractional_part(3.1415).repr) # => 0.1415
stdout.print_line(fractional_part(10.00).repr) # => 0.00
fractional_part('foo') # => N must be a num, but was "foo"
Two way branch can be done by if
preloaded function.
Example:
:negative_or_not <- {(:N)
if(N < 0
{ 'negative' }
{ 'non-negative' }
)
}
stdout.print_line(negative_or_not(-1)) # => negative
stdout.print_line(negative_or_not(0)) # => non-negative
stdout.print_line(negative_or_not(1)) # => non-negative
Multiway branch can be done by branch
preloaded function.
Example:
:sign <- {(:N)
branch(
{ N < 0 } { 'negative' }
{ N == 0 } { 'zero' }
{ true } { 'positive' }
)
}
stdout.print_line(sign(-1)) # => negative
stdout.print_line(sign(0)) # => zero
stdout.print_line(sign(1)) # => positive
Note that the last condition { true }
is a catch-all condition.
So it works like an else-block.
If no condition is true,
branch
raises an exception.
:color_to_rgb <- {(:Color)
branch(
{ Color == 'red' } { '#ff0000' }
{ Color == 'green' } { '#00ff00' }
{ Color == 'blue' } { '#0000ff' }
)
}
stdout.print_line(color_to_rgb('red')) # => #ff0000
stdout.print_line(color_to_rgb('green')) # => #00ff00
stdout.print_line(color_to_rgb('blue')) # => #0000ff
color_to_rgb('purple') # => branch(...[$cond1 $then1 $cond2 $then2 ,,,]): no matching cond
5.5.2. Loops¶
Loop can be done by recursive tail calls. Example:
:factorial <- {(:N)
:loop <- {(:I :Product)
if(I == N
{ Product }
{ loop(I + 1 Product * I) }
)
}
loop(1 1)
}
stdout.print_line(factorial(10).repr) # => 362880
For the scope of loop
variable,
see local recursive function pattern
section.
Recursive calls of loop
does not cause stack overflow, because
loop
tail-callsif
,if
tail-calls the else-thunk, andthe else-thunk tail-calls
loop
.
Oftentimes, using a method of a container
or an iterator is an better option than
writing recursive tail calls by yourself.
For example, factorial
method above can be rewritten as follows.
:factorial <- {(:N)
1.up.take_front(N).fold(1){(:I :Product)
I * Product
}
}
stdout.print_line(factorial(10).repr) # => 362880
5.5.3. Return and break¶
When you want to return from a function or break a loop, you can use CONTROL.with_break.
Example: Return from a function:
:CONTROL.require_from('kink/')
:to_fizz_buzz <- {(:Num)
CONTROL.with_break{(:return)
if(Num % 15 == 0){ return('fizzbuzz') }
if(Num % 3 == 0){ return('fizz') }
if(Num % 5 == 0){ return('buzz') }
Num.show
}
}
1.up.take_front(20)
.map{(:N) to_fizz_buzz(N) }
.each{(:S) stdout.print(S + ' ') }
# => 1 2 fizz 4 buzz fizz 7 8 fizz buzz 11 fizz 13 14 fizzbuzz 16 17 fizz 19 buzz
Example: Break a loop:
:CONTROL.require_from('kink/')
:show_reciprocal_sum <- {(:Nums)
:Result = CONTROL.with_break{(:break)
Nums.fold(0){(:Sum :N)
if(N == 0
{ break(-99999) }
{ Sum + (1 / N).floor(3) }
)
}
}
stdout.print_line('reciprocal sum: {}'.format(Result))
}
show_reciprocal_sum([1 2 3 4 5]) # => reciprocal sum: 2.283
show_reciprocal_sum([3 2 1 0 (-1)]) # => reciprocal sum: -99999
When you just break the control, you should use CONTROL.with_break instead of directly calling reset and shift of KONT_TAG. It is because CONTROL.with_break takes care of finally blocks of CONTROL.with_finally.
:CONTROL.require_from('kink/')
CONTROL.with_break{(:break)
CONTROL.with_finally{(:finally)
finally{ stdout.print_line('finally') }
stdout.print_line('begin')
break(nada)
stdout.print_line('must not reach here')
}
}
# Output:
# begin
# finally
:KONT_TAG.require_from('kink/')
:Kont_tag <- KONT_TAG.new
Kont_tag.reset{
CONTROL.with_finally{(:finally)
finally{ stdout.print_line('finally') }
stdout.print_line('begin')
Kont_tag.shift{}
stdout.print_line('must not reach here')
}
}
# Output:
# begin
5.5.4. Release resources¶
When you acquire a resource, ensure it is released using
with_finally
function of CONTROL module.
In the next example, the file input and the file output are ensured to be closed after being used.
:FILE.require_from('kink/io/')
:CONTROL.require_from('kink/')
# copy /etc/os-release to /tmp/os-release-copy
CONTROL.with_finally{(:finally)
:In = FILE.open_to_read('/etc/os-release')
finally{ In.close }
:Out = FILE.open_to_write('/tmp/os-release-copy')
finally{ Out.close }
:loop <- {
:Max_size = 100
In.read(Max_size){(:C)
C.on_present{(:Bin)
Out.write_bin(Bin)
loop
}
C.on_absent{}
}
}
loop
}
Thunks given to finally
are guaranteed to be executed,
when the body function given to with_finally
, and other finally
thunks
return, raise an exception, or jumped out by CONTROL.with_break.
finally
thunks are not executed,
when aborted by shift
of kont_tag type.
When multiple exceptions are raised from the body function
and finally
thunks, they are chained as a single exception.
See next
and have_next?
of exception type.
Example:
:CONTROL.require_from('kink/')
CONTROL.with_finally{(:finally)
finally{ raise('baz') }
finally{ raise('bar') }
raise('foo')
}
# Output:
# -- chained exception 1/2
# [(root)]
# ,,,
# {(stdin) L5 C14 raise} finally{ -->raise('baz') }
# baz
# -- chained exception 2/2
# [(root)]
# ,,,
# {(stdin) L6 C14 raise} finally{ -->raise('bar') }
# bar
# -- main exception
# [(root)]
# ,,,
# {(stdin) L7 C5 raise} -->raise('foo')
# foo