Lowent 매뉴얼←↑→

7 흐름 — 갈래, 되풀이, 빠져나가기

먼저 알아야 할 것

4장 수 · 참거짓은 수가 아니다
6장 지역 · if 는 값을 내지 않는 문장이다

돌아보기

6장에서 let x u64 if gt a 1 . 5 else 6 . 은 왜 거절되었고, 갈래마다 값을 정하려면 어떻게 적는가?

답. if 는 값을 내지 않는 문장이라서 식의 자리에 둘 수 없다(E-IF-VALUE). 갈래마다 값을 정하려면 var 를 기본값으로 짓고 갈래 안에서 set 하거나, 갈래마다 return 한다. 이 장은 그 갈래 자체와, 되풀이와 빠져나가기의 규칙을 다룬다.

이 장의 필요성과 맥락

흐름의 문장은 어느 언어에나 있어서 새로 배울 것이 없어 보인다. 그러나 Lowent 의 흐름 문장에는 검사가 붙어 있다. 조건은 참거짓이어야 하고, guard 의 else 는 반드시 떠나야 하며, 값을 돌려주는 op 은 모든 길에서 돌려주어야 하고, match 는 모든 경우를 덮어야 한다. 이 검사들은 “어떤 길로 가면 값이 없다” 는 결함을 번역할 때 없앤다. 데이터(제3부)로 넘어가기 전에 흐름의 약속을 먼저 세운다.

이 장이 끝나면

if … else, while, for 와 수를 세는 repeat·range·cycle, break·continue 를 쓰는 법을 익힌다. guard 가 조건을 그 아래 코드에 대한 사실로 바꾸는 방식과, else 가 떠나지 않으면 왜 거절되는지 알게 된다. 모든 길이 값을 돌려주어야 한다는 규칙, 수와 범위를 가르는 match 와 그 망라 검사, 그리고 panic 이 효과라는 것도 보게 된다.

이 장에서 답할 질문

  1. match 가 if 사슬보다 나은 점은 망라 검사 말고 무엇인가?

7.1 조건과 되풀이#

if 는 조건이 참일 때 블록을 실행하고, else 로 거짓일 때의 블록을 적는다. while 은 조건이 참인 동안 되풀이하고, for 는 슬라이스의 원소를 차례로 훑는다. 조건은 모두 bool 이어야 한다.

examples/ch07/loops.low

module loops .
rem run: count_big 10
rem run: first_zero [5,3,0,9]
rem run: first_zero [5,3]
rem run: odd_sum [1,2,3,4,5]

fn count_big input n u32 . output u32 .
  requires le n. 100 . .
do
  var total u32 0 .
  var i u32 0 .
  while lt i. n. . do
    if gt i. 5 . do
      set total. add total. 1 . .
    end .
    set i. add i. 1 . .
  end .
  return total. .
end .

fn first_zero input xs slice u8 . output usize .
do
  var i usize 0 .
  while lt i. len xs. . . do
    if eq idx xs. i. . 0 . do
      break .
    end .
    set i. add i. 1 . .
  end .
  return i. .
end .

fn odd_sum input xs slice u8 . output u64 .
do
  var acc u64 0 .
  for x xs. do
    if eq mod x. 2 . 0 . do
      continue .
    end .
    set acc. add acc. widen u64 x. . . .
  end .
  return acc. .
end .

실행 결과

$ lowentc --run count_big loops.low 10
count_big(10) = 4
$ lowentc --run first_zero loops.low [5,3,0,9]
first_zero([5,3,0,9]) = 2
  arg0 (written) = [5,3,0,9]
$ lowentc --run first_zero loops.low [5,3]
first_zero([5,3]) = 2
  arg0 (written) = [5,3]
$ lowentc --run odd_sum loops.low [1,2,3,4,5]
odd_sum([1,2,3,4,5]) = 9
  arg0 (written) = [1,2,3,4,5]

for x in xs 처럼 in 을 적으면 E-VOCAB-REMOVED 로 거절된다. 훑을 대상은 이름 바로 뒤에 온다.

수를 세는 반복은 제 머리말이 있다. repeat i u64 n. do 는 0 부터 n 미만까지, range i u64 a. b. do 는 a 부터 b 까지 두 끝을 넣어 돈다. 다음 값을 직접 적으려면 cycle i u64 1 while lt i. 100 . next mul i. 2 . do 로 쓴다. 셈의 타입(u64)은 늘 적는다. 머리 안에는 마침표가 없다 — 식은 do·step·while·next 같은 낱말에서 끝난다. 원소를 거르려면 do 앞에 if <조건> 을 붙인다(for x xs. if gt x. 2 . do). for 는 원소를 도는 한 가지 일만 한다 — 옛 꼴 for i count u64 n . do 는 E-FOR-OLD 로 거절된다.

examples/ch07/counting.low

module counting .
rem run: tally 4

rem 수를 세는 되풀이 --- `repeat` 는 끝을 빼고, `range` 는 두 끝을 넣는다
export fn tally input n u64 . output u64 .
do
  var s u64 0 .
  repeat i u64 n. do
    set s. add s. i. . .
  end .
  range j u64 10 1 step -3 do
    set s. add mul s. 100 . j. . .
  end .
  return s. .
end .

실행 결과

$ lowentc --run tally counting.low 4
tally(4) = 610070401

examples/ch07/doubling.low

module doubling .
rem run: steps 100

rem 다음 값을 직접 적는 되풀이 --- 1 에서 시작해 `limit` 에 닿기 전까지 두 배씩
export fn steps input limit u64 . output u64 .
  requires le limit. 1000000 . .
do
  var n u64 0 .
  cycle i u64 1 while lt i. limit. . next mul i. 2 . do
    set n. add n. 1 . .
  end .
  return n. .
end .

실행 결과

$ lowentc --run steps doubling.low 100
steps(100) = 7

7.1.1 겹친 되풀이를 한 번에 벗어난다#

break 와 continue 뒤에 수를 적으면 그 수만큼 바깥으로 센 되풀이에 듣는다. 1 은 가장 안쪽이고(수를 적지 않은 것과 같다), 2 는 그것을 감싼 되풀이다. 되풀이에 이름을 붙이는 라벨은 없다.

examples/ch07/breakout.low

module breakout .
rem run: first_cell 8

rem 겹친 되풀이에서 찾던 칸을 만나면 `break 2 .` 로 둘을 한 번에 벗어난다
export fn first_cell input want u32 . output usize .
do
  let g lit slice array 3 u32 lit array 3 u32 1 2 3 . lit array 3 u32 4 0 6 . lit array 3 u32 7 8 9 . . .
  var hit usize 99 .
  repeat r usize len g. . do
    repeat c usize 3 do
      if eq idx idx g. r. . c. . want. . do
        set hit. add mul r. 3 . c. . .
        break 2 .
      end .
    end .
  end .
  return hit. .
end .

rem `continue 2 .` 는 안쪽 되풀이를 벗어나 바깥 되풀이의 다음 바퀴로 간다
export fn rows_without input bad u32 . output u64 .
do
  let g lit slice array 3 u32 lit array 3 u32 1 2 3 . lit array 3 u32 4 0 6 . lit array 3 u32 7 8 9 . . .
  var n u64 0 .
  for row g. do
    for x row. do
      guard ne x. bad. . else continue 2 . .
    end .
    set n. add n. 1 . .
  end .
  return n. .
end .

실행 결과

$ lowentc --run first_cell breakout.low 8
first_cell(8) = 7

수가 감싼 되풀이보다 많으면 거절된다.

examples/ch07/mistake_breakfar.low

module mistake_breakfar .
rem expect: E-LOOP-COUNT

fn f output u64 .
do
  var n u64 0 .
  repeat a u64 3 do
    repeat b u64 3 do
      set n. add n. 1 . .
      rem ✘ 감싼 되풀이는 둘뿐이다 --- 셋을 벗어날 수 없다
      break 3 .
    end .
  end .
  return n. .
end .

실행 결과

$ lowentc --check mistake_breakfar.low
mistake_breakfar.low:11:0 E-LOOP-COUNT: the number after `break` / `continue` counts the loops around it: 1 is the innermost (the same as writing no number), 2 is the loop around that one, and so on. It is a plain decimal literal from 1 up to the number of loops this statement sits in — there is no loop that far out here

7.2 guard — 조건을 사실로 바꾼다#

guard <조건> else <빠져나감> . 은 조건이 참이 아니면 그 자리에서 떠난다. 떠나는 문장은 return·break·continue·panic 이다.

examples/ch07/guards.low

module guards .
rem run: head_or_zero [7,8]
rem run: head_or_zero []
rem run: grade 95
rem run: grade 42

fn head_or_zero input data slice u8 . output u8 .
do
  guard ge len data. . 1 . else return 0 . .
  return idx data. 0 . .
end .

fn grade input score u8 . output u8 .
do
  guard le score. 100 . else return 0 . .
  if ge score. 90 . do
    return 65 .
  end else do
    if ge score. 60 . do
      return 66 .
    end .
  end .
  return 70 .
end .

실행 결과

$ lowentc --run head_or_zero guards.low [7,8]
head_or_zero([7,8]) = 7
  arg0 (written) = [7,8]
$ lowentc --run head_or_zero guards.low []
head_or_zero([]) = 0
  arg0 (written) = []
$ lowentc --run grade guards.low 95
grade(95) = 65
$ lowentc --run grade guards.low 42
grade(42) = 70

head_or_zero 의 guard 를 지난 코드는 슬라이스가 비지 않은 세계에서만 산다. 그래서 idx data. 0 . 이 안전하다. grade 는 guard 로 범위 밖을 먼저 걸러 내고, 그 아래에서 if … end else do … end 로 갈래를 나눈다.

guard 가 if not 의 다른 이름이 아닌 까닭은 else 가 반드시 떠나야 한다는 데 있다. 떠나지 않으면 거절된다.

examples/ch07/fallthrough.low

module fallthrough .
rem expect: E-GUARD-FALLTHROUGH

fn clamp5 input n u32 . output u32 .
do
  guard le n. 5 . else do
    let capped u32 5 .
  end .
  return n. .
end .

실행 결과

$ lowentc --check fallthrough.low
fallthrough.low:6:0 E-GUARD-FALLTHROUGH: this `guard`'s `else` FALLS THROUGH — it must LEAVE (`return` / `break` / `continue` / `panic`). If it falls through, the code AFTER the guard wrongly believes the condition holds. Leaving is the WHOLE point of `guard`: it is what lets the rest of the op assume the condition. (SPEC-002 D6 always said so and nothing enforced it — which made `guard` an exact synonym of `if not`.) If you do not mean to leave, write `if`

else 가 떠나지 않고 아래로 흘러내리면, guard 아래 코드는 n 이 5 이하라고 믿지만 실제로는 그렇지 않다. 믿을 수 없는 사실을 믿게 두느니 번역을 거절한다. 떠나지 않을 일이면 if 를 쓴다.

흔한 오해. guard 는 코드를 짧게 하는 문법 설탕이다

짧아지는 것은 부수 효과다. guard 의 본뜻은 컴파일러와 읽는 사람에게 사실 하나를 건네는 것이다. 컴파일러는 guard 를 지난 뒤 그 조건을 참으로 알고 경계 검사를 지우는 데 쓴다. 같은 뜻을 if … do return … end 로 적어도 동작은 같지만, 떠남이 문법으로 보장되지는 않는다.

7.3 모든 길이 값을 돌려준다#

값을 돌려주는 op 은 모든 길에서 값을 돌려주고 끝나야 한다.

examples/ch07/partial.low

module partial .
rem expect: E-RETURN-PARTIAL

fn sign_flag input a i32 . output u8 .
do
  if gt a. 0 . do
    return 1 .
  end .
end .

실행 결과

$ lowentc --check partial.low
partial.low:4:0 E-RETURN-PARTIAL: this op says it OUTPUTS a value, but some path through its body reaches the end without a `return`. Until now the tool quietly returned 0 there — a value that appears NOWHERE in your source (RFC-0019 G-TOTAL). Give every path a `return`, or say `output void` if it really produces nothing. An exhaustive `match` whose every arm returns counts as returning

a 가 0 이하인 길에는 return 이 없다. 옛 도구는 그 길에서 조용히 0 을 돌려주었다. 소스 어디에도 없는 값이다. 지금은 거절된다. 값을 돌려주고 끝나는 것으로 인정되는 문장은 return, 두 갈래가 모두 돌려주는 if … else, 모든 갈래가 돌려주는 match 다. while·for·guard 는 빠져나가는 길이 있으므로 그 자체로 돌려주는 것으로 보지 않는다. 그래서 반복 뒤에는 늘 return 이 온다.

돌려줄 값이 없는 op(output void)은 return 없이 몸의 끝까지 가도 된다. 끝나는 자리가 곧 돌아가는 자리다.

7.4 match — 경우를 빠짐없이#

match 는 값을 경우별로 가른다. if 사슬과 다른 점은 모든 경우를 다뤄야 한다는 것이다.

examples/ch07/bands.low

module bands .
rem run: band 9
rem run: band 10
rem run: band 200
rem run: half 128
rem run: big 101

fn band input x u8 . output u8 .
do
  match x. do
    case 0 to 9 do return 1 . end .
    case 10 to 19 do return 2 . end .
    case _ do return 0 . end .
  end .
end .

fn half input b u8 . output u8 .
do
  match b. do
    case 0 to 127 do return 0 . end .
    case 128 to 255 do return 1 . end .
  end .
end .

fn big input x u8 . output u8 .
do
  match x. do
    case y when gt y 100 do return 1 . end .
    case _ do return 0 . end .
  end .
end .

실행 결과

$ lowentc --run band bands.low 9
band(9) = 1
$ lowentc --run band bands.low 10
band(10) = 2
$ lowentc --run band bands.low 200
band(200) = 0
$ lowentc --run half bands.low 128
half(128) = 1
$ lowentc --run big bands.low 101
big(101) = 1

빈틈이 있으면 거절된다.

examples/ch07/inexhaustive.low

module inexhaustive .
rem expect: E-MATCH-INEXHAUSTIVE

fn band input x u8 . output u8 .
do
  match x. do
    case 0 to 9 do return 1 . end .
    case 11 to 255 do return 2 . end .
  end .
end .

실행 결과

$ lowentc --check inexhaustive.low
inexhaustive.low:6:0 E-MATCH-INEXHAUSTIVE: this `match` on an integer is not exhaustive — the literals/ranges leave a gap, so a `case _ .` wildcard (or `else`) is required, OR the ranges must TILE the whole domain of the scrutinee's type (e.g. `0 to 127` + `128 to 255` on a u8) (RFC-0020 §6.4; MM4 tiling)

10 이 어느 갈래에도 들지 않는다. 같은 경우를 두 번 적거나 _ 뒤에 갈래를 두면 E-MATCH-REDUNDANT 로 거절된다. 경고가 아니라 오류다 — 한 번도 실행될 수 없는 갈래는 대개 결함을 숨긴다.

문. match 가 if 사슬보다 나은 점은 망라 검사 말고 무엇인가?

답. 나중에 경우가 늘어날 때 드러난다. 열거형에 변형을 하나 더하면, 그 열거형을 가르는 모든 match 가 번역에서 멈춰 고쳐야 할 자리를 알려 준다(10장). if 사슬은 새 경우를 조용히 마지막 else 로 흘려보낸다. 또 --ir 로 보면 도구가 갈래 수와 최악 비교 횟수 같은 디스패치 비용을 말해 준다.

7.5 panic 은 효과다#

panic 은 프로그램을 즉시 멈춘다. 되돌릴 수 없는 멈춤이므로 효과이고, 쓰는 op 은 effects panic 을 적는 proc 이어야 한다.

examples/ch07/panics.low

module panics .
rem run: checked 3
rem trap: checked 0

proc checked input n u32 . output u32 . effects panic .
do
  if eq n. 0 . do
    panic "n must not be zero" .
  end .
  return div 100 n. . .
end .

실행 결과

$ lowentc --run checked panics.low 3
checked(3) = 33
$ lowentc --run checked panics.low 0
== ir diagnostics (1) ==
0:0 E-VM-PANIC: the program called `panic` — this is an unrecoverable stop, and it is NOT a contract violation (the code chose to stop, it did not break a promise)

VM 은 E-VM-PANIC 을 내며, 이것이 계약 위반이 아니라는 것을 분명히 한다 — 코드가 멈추기로 고른 것이지 약속을 어긴 것이 아니다. 순수한 fn 은 panic 을 쓸 수 없다. 다만 넘침이나 계약 위반으로 처리기가 일으키는 멈춤은 fn 에서도 일어난다. 그것은 op 이 한 일이 아니라 처리기가 약속을 지키게 한 일이다.

panic 은 복구할 수 없는 상황에만 쓴다. 호출자가 다룰 수 있는 실패는 값으로 돌려준다 (11장). 둘을 가르는 기준은 17장이 다룬다.

7.6 흔한 실수#

반례. for x in xs 로 적는다

examples/ch07/mistake_forin.low

module mistake_forin .
rem expect: E-VOCAB-REMOVED

fn total input xs slice u8 . output u64 .
do
  var t u64 0 .
  rem ✘ `in` 을 적었다 --- `for` 는 `for <이름> <슬라이스> do` 다
  for x in xs. do
    set t. add t. widen u64 x. . . .
  end .
  return t. .
end .

실행 결과

$ lowentc --check mistake_forin.low
8:9 E-VOCAB-REMOVED: `in` is gone — a loop over elements is `for <name> <slice>. do … end .` — found `in` (RFC-0142: a name opens, a stop closes; a variable is written `a.`)

for 는 for <이름> <슬라이스> do 다. 훑을 대상이 이름 바로 뒤에 오고, 몸의 시작은 do 가 알리므로 in 이 전할 것이 없다. 같은 뜻에 표시를 하나 더 두지 않으려고 없앴다. 고치는 법: for x xs. do.

반례. 조건과 do 사이에 마침표를 하나 더 찍는다

examples/ch07/mistake_dotdo.low

module mistake_dotdo .
rem expect: E-CLOSER-EXTRA

fn big input a u64 . output u64 .
do
  rem ✘ 조건 뒤에 마침표를 하나 더 찍었다 --- 조건은 제 점으로 이미 닫혔고, 그 다음은 `do` 다
  if gt a. 3 . . do
    return 1 .
  end .
  return 0 .
end .

실행 결과

$ lowentc --check mistake_dotdo.low
7:16 E-CLOSER-EXTRA: this stop closes nothing — the value before it is already closed, and the block `do … end` comes next — found `.` (RFC-0142: a name opens, a stop closes; a variable is written `a.`)

조건은 값이고, 값은 제 점으로 닫힌다 — gt a. 3 . 의 마지막 점이 그것이다. 그 다음에는 do 가 온다. 점을 하나 더 찍으면 닫을 것이 없는 점이라 E-CLOSER-EXTRA 로 거절된다. while·match 의 머리와 guard 의 조건도 같다. guard gt a. 0 . else return 0 . . 에서 점 셋은 차례로 조건, return, guard 를 닫는다. 고치는 법: if gt a. 3 . do.

반례. 수를 for … count 로 센다

examples/ch07/mistake_forcount.low

module mistake_forcount .
rem expect: E-FOR-OLD

fn tally input n u64 . output u64 .
do
  var s u64 0 .
  rem ✘ 옛 꼴 --- 수를 세는 일은 `repeat` 가 한다. `for` 는 원소만 돈다
  for i count u64 n. do
    set s. add s. i. . .
  end .
  return s. .
end .

실행 결과

$ lowentc --check mistake_forcount.low
8:3 E-FOR-OLD: `for` walks elements only — counting is `repeat <name> <type> <n>. do … end .`, a range is `range <name> <type> <a>. <b>. do … end .` — found `for` (RFC-0142: a name opens, a stop closes; a variable is written `a.`)

for 는 원소를 도는 한 가지 일만 한다. 수를 세는 일은 repeat, 구간은 range, 다음 값을 직접 적는 되풀이는 cycle 이 한다 — 머리말을 보면 무슨 되풀이인지 안다. 고치는 법: repeat i u64 n. do.

반례. elif 로 갈래를 잇는다

examples/ch07/mistake_elif.low

module mistake_elif .
rem expect: E-DOT-MISSING

fn grade input n u64 . output u64 .
do
  if lt n. 50 . do
    return 0 .
  rem ✘ `elif` 는 낱말이 아니다 --- 이어지는 갈래는 `end else if … do` 다
  end elif lt n. 80 . do
    return 1 .
  end .
  return 2 .
end .

실행 결과

$ lowentc --check mistake_elif.low
9:7 E-DOT-MISSING: a stop `.` must close `if … end` — found `elif` (RFC-0142: a name opens, a stop closes; a variable is written `a.`)

elif·elsif·else if: 는 언어마다 다르다. Lowent 는 이미 있는 낱말을 이어 붙인다 — 앞 블록을 end 로 닫고 else if 를 붙인다. elif 는 낱말이 아니다. end 뒤에는 else 가 오거나 폼을 닫는 점이 와야 하므로, elif 를 만난 자리에서 E-DOT-MISSING 으로 거절된다.

examples/ch07/elseif.low

module elseif .
rem run: grade 30
rem run: grade 70
rem run: grade 95

fn grade input n u64 . output u64 .
do
  if lt n. 50 . do
    return 0 .
  rem 갈래를 이을 때는 앞 블록을 `end` 로 닫고 `else if` 를 붙인다
  end else if lt n. 80 . do
    return 1 .
  end else do
    return 2 .
  end .
end .

실행 결과

$ lowentc --run grade elseif.low 30
grade(30) = 0
$ lowentc --run grade elseif.low 70
grade(70) = 1
$ lowentc --run grade elseif.low 95
grade(95) = 2

마지막 갈래는 end else do … end . 이다. 갈래가 셋 이상이고 모두 한 값을 가르는 것이면 match 가 더 알맞다.

반례. C 처럼 블록 안에 else 를 둔다

examples/ch07/mistake_innerelse.low

module mistake_innerelse .
rem expect: E-STMT-ELSE

fn pick input a u8 . output u8 .
do
  var r u8 0 .
  if gt a. 5 . do
    set r. 1 .
  rem ✘ C 처럼 블록 안에 `else` 를 한 줄로 두었다 --- `end else do` 로 앞 블록을 닫아야 한다
  else
    set r. 2 .
  end .
  return r. .
end .

실행 결과

$ lowentc --check mistake_innerelse.low
10:3 E-STMT-ELSE: `else` belongs after the `end` of the block before it — `if c. do … end else do … end .` — found `else` (RFC-0142: a name opens, a stop closes; a variable is written `a.`)

C 나 몇몇 언어는 if … { … } else { … } 처럼 else 가 앞 블록 뒤에 붙는다. Lowent 에서 else 는 앞 블록을 end 로 닫은 뒤에 온다(end else do). 블록 안에 else 를 한 줄로 두면 E-STMT-ELSE 로 거절한다 — 전에는 번역이 통과하고 실행에서야 E-VM-UNSUP 으로 멈췄으며, 네이티브 빌드는 그 op 을 아예 뺐다.

반례. 조건에 < 같은 기호를 쓴다

examples/ch07/mistake_less.low

module mistake_less .
rem expect: E-CHAR

fn upto input n u64 . output u64 .
do
  var i u64 0 .
  rem ✘ `<` 는 이 언어의 기호가 아니다 --- 비교는 `lt i n` 이다
  while i. < n. do
    set i. add i. 1 . .
  end .
  return i. .
end .

실행 결과

$ lowentc --check mistake_less.low
8:12 E-CHAR: unexpected character

비교는 낱말이다 — lt(작다)·le(작거나 같다)·gt·ge·eq·ne. < 는 이 언어가 모르는 글자라 E-CHAR 가 난다. 기호의 우선순위를 외우지 않아도 되게 하려는 선택이고, 긴 산술에는 expr 섬이 있다(8장). 고치는 법: while lt i. n. . do.

반례. continue 가 증가를 건너뛴다

while 로 세면서 몸 가운데서 continue 하면, 그 아래에 둔 set i. add i. 1 . . 도 함께 건너뛴다.

fn odd_count input xs slice u8 . output u64 .
do
  var n u64 0 .
  var i usize 0 .
  while lt i. len xs. . . do
    if eq mod idx xs. i. . 2 . 0 . do
      continue .
    end .
    set n. add n. 1 . .
    set i. add i. 1 . .
  end .
  return n. .
end .

첫 짝수에서 i 가 더 이상 오르지 않아 반복이 끝나지 않는다. 번역도 실행 검사도 이것을 잡지 못한다 — 멈추지 않는 것은 넘침이 아니기 때문이다. 증가를 몸의 맨 앞으로 옮기거나(그러면 색인에는 증가 전의 값을 따로 담는다), 원소를 훑는 일이면 처음부터 for x xs. do 를 쓴다. for 는 다음 원소로 넘어가는 일을 언어가 맡으므로 이 결함이 생길 자리가 없다.

나머지를 받는 자리는 두 가지로 적을 수 있다. case _ 와 마지막 else 는 같은 일을 한다.

examples/ch07/matchelse.low

module matchelse .
rem run: band 1
rem run: band 3
rem run: band 99

fn band input n u64 . output u64 . do
  match n. do
    case 1 do
      return 10 .
    end .
    case 2 to 5 do
      return 20 .
    end .
    rem 나머지를 `else` 로 받는다 --- `case _ .` 와 같은 자리다
    else do
      return 99 .
    end
  end .
end .

실행 결과

$ lowentc --run band matchelse.low 1
band(1) = 10
$ lowentc --run band matchelse.low 3
band(3) = 20
$ lowentc --run band matchelse.low 99
band(99) = 99

else 뒤에는 갈래를 더 둘 수 없다 — 이미 다 받았으므로 그 뒤의 갈래는 한 번도 돌지 않고, E-MATCH-REDUNDANT 로 거절된다.

흔한 오해. match 의 갈래도 C 의 switch 처럼 아래로 흘러간다

examples/ch07/nofall.low

module nofall .
rem run: label 1
rem run: label 2
rem run: label 9

fn label input c u64 . output u64 .
do
  var score u64 0 .
  match c. do
    rem 맞은 갈래 하나만 실행된다 --- 다음 갈래로 흘러가지 않으므로 `break` 가 필요 없다
    case 1 do set score. add score. 10 . . end .
    case 2 do set score. add score. 20 . . end .
    case _ do set score. add score. 1 . . end .
  end .
  return score. .
end .

실행 결과

$ lowentc --run label nofall.low 1
label(1) = 10
$ lowentc --run label nofall.low 2
label(2) = 20
$ lowentc --run label nofall.low 9
label(9) = 1

맞은 갈래 하나만 실행되고 다음 갈래로 흘러가지 않는다. break 를 적는 버릇도, 빠뜨려서 생기는 결함도 없다. 두 경우에 같은 일을 하려면 갈래를 or 로 묶는다(11장).

7.7 이 장의 문법을 한눈에#

모양뜻왜 이렇게
if c do … end .조건이 참이면 블록조건은 do 에서 끝난다 — 마침표는 문장만 닫는다
if c do … end else do … end .둘 중 하나여는 말과 닫는 말이 늘 짝을 이룬다
… end else if c2 do … end .갈래 잇기새 낱말(elif) 없이 있는 낱말을 잇는다
while c do … end .조건이 참인 동안조건을 바퀴마다 다시 본다
repeat i u64 n. do … end . · range i u64 a. b. do … end .0 부터 n 번 · a 에서 b 까지수를 세는 일은 머리말이 맡는다 — 셈 변수를 손으로 올리지 않는다
cycle i u64 v while c next e do … end .다음 값을 직접 적는 되풀이시작 · 조건 · 다음 값이 머리 한 줄에 모인다
for x xs. do … end .슬라이스의 원소를 차례로다음 원소로 넘어가는 일을 언어가 맡는다
break . · continue .반복에서 나가기 · 다음 회차로흐름을 바꾸는 문장
break 2 . · continue 2 .두 겹째 되풀이에서 나가기 · 그 되풀이의 다음 회차로깃발 변수 없이 겹친 되풀이를 벗어난다
guard c else return … .조건이 아니면 떠난다 — 지난 뒤엔 조건이 사실else 가 반드시 떠나야 한다
return e .값을 돌려주고 끝낸다값을 내는 op 은 모든 길에서
match v. do case … do … end . … end .경우별로 가르기빠짐없이, 겹침 없이 — 흘러내림이 없다
case 1 to 9 · case _ · case y when c범위 · 나머지 전부 · 가드가 붙은 갈래정수는 경우가 많아 _ 가 흔히 필요하다
panic "…" .되돌릴 수 없는 멈춤(효과)effects panic 을 적는 proc 에서만

표 7.1 — 흐름의 문법 — 모양 · 뜻 · 왜 이렇게 생겼나

복습 정리

조건은 bool 이어야 한다. while 은 조건으로, for 는 슬라이스로 되풀이하고 break·continue 로 흐름을 바꾼다. guard 의 else 는 반드시 떠나고, 지난 뒤에는 조건이 사실이 된다. 값을 돌려주는 op 은 모든 길에서 돌려주어야 한다. match 는 빠짐없이, 겹침 없이 경우를 덮어야 한다. panic 은 효과다.