Lowent 매뉴얼←↑→

5 op — fn 과 proc

먼저 알아야 할 것

3장 겉모습 · op 머리의 절 차례
4장 수 · 넘침은 멈추고 처분은 이름으로 고른다

돌아보기

3장의 절 차례표에서 output 과 effects 는 각각 무엇의 뒤에 오는가? 왜 그 자리인가?

답. output 은 데이터 입력 뒤에 온다. 출력 타입이 입력의 타입 매개변수를 쓸 수 있기 때문이다. effects 는 output 뒤, 계약 앞에 온다. 앞에서 받은 권한으로 무엇을 하는지를 적는 자리이기 때문이다. 이 장은 그 머리를 가진 실행 단위 — op — 가 두 갈래로 나뉘는 까닭을 다룬다.

이 장의 필요성과 맥락

Lowent 프로그램의 기본 단위는 op 이고, op 은 언제나 fn 이거나 proc 이다. 이 갈래는 제4부의 효과 체계, 제6부의 트레이트, 제7부의 병렬이 모두 기대는 바닥이다. 순수한 op 은 결과를 기억해 두어도, 차례를 바꾸어도, 나누어 돌려도 되기 때문이다. 그래서 수를 다룬 바로 다음, 지역과 흐름보다 먼저 op 의 두 갈래를 세운다.

이 장이 끝나면

fn 과 proc 의 차이, 그리고 순수함이 관측적이라는 뜻(안에 갇힌 변이는 순수)을 알게 된다. 매개변수를 input 절로 받고 output 으로 돌려주는 법, 재귀, neg 가 유일한 단항 산술이라는 것을 익힌다. 순수한 op 이 비순수한 op 을 부를 때와 mut 매개변수로 호출자에게 쓸 때 무엇이 거절되는지도 보게 된다.

이 장에서 답할 질문

  1. proc 인데 실제로 아무 효과도 내지 않으면 어떻게 되는가?

5.1 두 갈래#

op(operation)은 이름과 계약을 가진 실행 단위다. 표면에서는 언제나 두 낱말 가운데 하나로 연다.

기본값은 없다. fn 인지 proc 인지는 이름 앞에 늘 적혀 있다. add 나 len 같은 기본 연산도 op 이다 — 언어가 미리 만들어 둔 op 이라서 기본 연산 이라 부른다.

examples/ch05/kinds.low

module kinds .
rem run: add3 1 2 3
rem run: running_total [4,5,6]
rem run: fact 10

fn add3 input a i32 . input b i32 . input c i32 . output i32 .
do
  return add add a. b. . c. . .
end .

rem 지역 가변 상태는 op 안에 갇혀 있으므로 fn 도 쓸 수 있다
fn running_total input xs slice u8 . output u64 .
do
  var total u64 0 .
  for x xs. do
    set total. add total. widen u64 x. . . .
  end .
  return total. .
end .

fn fact input n u64 . output u64 .
  requires le n. 20 . .
do
  if le n. 1 . do return 1 . end .
  return mul n. fact sub n. 1 . . . .
end .

실행 결과

$ lowentc --run add3 kinds.low 1 2 3
add3(1, 2, 3) = 6
$ lowentc --run running_total kinds.low [4,5,6]
running_total([4,5,6]) = 15
  arg0 (written) = [4,5,6]
$ lowentc --run fact kinds.low 10
fact(10) = 3628800

세 op 이 모두 fn 이다. add3 은 인자 셋을 받아 더하고, fact 는 자기 자신을 부르는 재귀다. fact 의 requires le n. 20 . . 은 21! 이 u64 를 넘기 때문에 붙인 계약이다. 이 계약이 없으면 fact 21 은 곱셈에서 멈춘다(4장). 계약이 있으면 진입에서 멈추고, 멈춘 자리가 부르는 쪽의 잘못임을 알린다.

5.2 순수함은 관측으로 정한다#

running_total 을 다시 보자. 본문에 var total 이 있고 set 으로 값을 바꾼다. 그런데도 fn 이다. Lowent 의 순수함은 관측적이기 때문이다. op 안에 갇힌 지역 변수의 변이는 바깥에서 볼 수 없으므로 순수로 친다. 기계어 수준에서 메모리에 쓰는 일이 일어나도, 호출자가 관측할 수 없으면 효과가 아니다.

반대로 호출자가 볼 수 있는 쓰기는 효과다. mut 매개변수를 통해 쓰는 fn 은 거절된다.

examples/ch05/mutparam.low

module mutparam .
rem expect: E-EFFECT-PURITY

fn zero_first input xs mut slice u64 . output u64 .
do
  set idx xs. 0 . 0 .
  return 0 .
end .

실행 결과

$ lowentc --check mutparam.low
mutparam.low:4:0 E-EFFECT-PURITY: a `fn` WRITES through a `mut` parameter — that write is visible to the CALLER. A fn is an enforced purity contract (SPEC-003 §27): callers may memoise it, reorder it, or elide it. An op that changes caller-owned storage can do none of those. Declare it a `proc`. (Local mutation stays pure: storage confined to the op is not observable — a machine write is not an observable effect. RFC-0057)

진단이 설명하는 대로, fn 은 강제되는 순수 계약이다. 호출자는 fn 의 결과를 기억해 두거나(memoise), 부르는 차례를 바꾸거나, 결과를 쓰지 않으면 호출을 지워도 된다. 호출자의 저장소를 바꾸는 op 에는 그 어느 것도 할 수 없다. 그래서 proc 이어야 한다.

examples/ch05/mutproc.low

module mutproc .
rem run: zero_first [7,8,9]

proc zero_first input xs mut slice u8 . output usize . effects state .
  requires gt len xs. . 0 . .
do
  set idx xs. 0 . 0 .
  return len xs. . .
end .

실행 결과

$ lowentc --run zero_first mutproc.low [7,8,9]
zero_first([0,8,9]) = 3
  arg0 (written) = [0,8,9]

effects state . 는 이 op 이 자기 바깥의 상태를 고친다는 선언이다. VM 이 보여 주는 arg0 (written) = [0,8,9] 가 그 흔적이다 — 호출자가 넘긴 슬라이스의 첫 원소가 바뀌었다.

흔한 오해. proc 은 fn 보다 느리다

갈래는 속도와 상관이 없다. 둘 다 같은 방식으로 C 로 내려간다. 차이는 컴파일러와 호출자가 할 수 있는 일에 있다. fn 에는 차례 바꾸기·공통 부분식 제거·기억해 두기 같은 최적화가 허락되고, proc 에는 허락되지 않는다. 순수한 일을 proc 으로 적으면 그 기회를 스스로 버리는 것이다.

5.3 fn 에는 effects 를 적지 않는다#

fn 의 효과는 이미 none 이다. 그래서 effects none . 을 덧붙이면 거절된다.

examples/ch05/redundant.low

module redundant .
rem expect: E-EFFECT-REDUNDANT

fn square input a u64 . output u64 . effects none .
do
  return mul a. a. . .
end .

실행 결과

$ lowentc --check redundant.low
redundant.low:4:0 E-EFFECT-REDUNDANT: a `fn` is pure by contract, so `effects none .` says nothing that the `fn` did not already say — DROP the clause. One meaning must have one spelling (SPEC-002 §2.5). (Keep writing `effects` on `proc`: there it NARROWS, and omitting it means unrestricted.)

뜻이 틀려서가 아니라 한 뜻에 두 표기가 되기 때문이다. proc 에서는 사정이 다르다. proc 의 effects 절은 좁히는 일을 한다. 그래서 proc 은 절을 반드시 적는다 — 없으면 E-EFFECT-MISSING 이고, 효과가 없으면 effects none . 이다. lowentc --doc 이 몸이 실제로 내는 효과(inferred:)를 알려 준다. 적은 것보다 많은 일을 하면 거절되며, 적어 놓고 하지 않는 효과도 알려진다.

문. proc 인데 실제로 아무 효과도 내지 않으면 어떻게 되는가?

답. proc 에 effects none . 을 적는 것은 허락된다. 지역 가변 상태나 반복을 강조하고 싶어서 proc 으로 두는 경우가 옛 코드에 있다. 그러나 관측되는 효과가 없다면 fn 으로 적는 것이 이 언어의 권하는 모양이다. 순수함이 머리에 드러나야 호출자와 컴파일러가 그것을 쓸 수 있다. 내보내지 않은 proc 이 effects none 이고 mut 입력도 없고 다른 proc 도 안 부르면 처리기가 W-PROC-PURE 로 알려 준다(RFC-0113 O3).

5.4 효과는 부르는 쪽으로 번진다#

op 을 부르면 그 op 의 효과가 부르는 쪽의 효과에 더해진다. 순수한 fn 이 io 를 내는 proc 을 부르면, 그 fn 은 더 이상 순수하지 않다.

examples/ch05/calls.low

module calls .
rem expect: E-EFFECT-CALC

use stdio .

proc say input out cap io . output usize . effects io .
do
  return stdio.write out. 1 "hi\n" . .
end .

fn helper input out cap io . output usize .
do
  return say out. . .
end .

실행 결과

$ lowentc --check calls.low
calls.low:12:1 E-EFFECT-CALC: this fn is declared pure but performs `io` — make it a `proc` with `effects …`, or remove the effect

helper 는 스스로 출력하지 않는다. 출력하는 것은 say 다. 그래도 거절된다. 이 규칙 덕분에 op 의 머리만 보고도 그 op 이 부르는 모든 op 의 효과를 알 수 있다. 효과를 숨긴 채 전달하는 길이 없다. 어떤 효과가 어떤 권한과 짝지어지는지는 15·16장에서 다룬다.

5.5 매개변수와 반환#

op 하나를 조각조각 뜯어 보면 이렇다. 처음 보는 사람이 가장 헷갈리는 것은 절마다 마침표로 끝난다는 점이다. 마침표는 문장의 끝처럼 “이 절은 여기까지” 를 알린다.

fn   add3   input a i32 .   input b i32 .   input c i32 .   output i32 .
│    │      └─ 매개변수 a 의 타입은 i32 ─┘                  └ 돌려주는 값의 타입
│    └─ 이름 (부를 때 쓴다)
└─ 갈래: fn(순수) 또는 proc(효과를 낼 수 있음)
do                                ← 몸이 여기서 시작한다
  return add add a. b. . c. . .   ← 계산해서 돌려준다. 값도 문장도 제 점으로 닫힌다
end .                             ← 몸이 끝나고, 점이 선언 전체를 닫는다

부를 때는 이름 뒤에 인자를 공백으로 늘어놓고, 그 호출을 닫는 점을 찍는다. add3 1 2 3 . 이 한 번의 호출이다. 호출을 다른 호출의 인자로 넣으면 안쪽 호출이 제 점으로 먼저 닫힌다 — add3 add3 1 2 3 . 4 5 .. 안쪽 호출을 눈에 띄게 하려면 괄호를 둘러도 된다 (add3 (add3 1 2 3 .) 4 5 .). 괄호는 뜻을 바꾸지 않는다. C 처럼 add3(1, 2, 3) 으로 쓰는 법은 없다. 쉼표는 이 언어에서 뜻이 없는 기호이고, 그래서 거절된다(아래 «흔한 실수»).

매개변수는 input <이름> <타입> . 한 절에 하나다. 여럿이면 절을 되풀이한다. 부르는 쪽은 절의 차례대로 인자를 준다. 돌려주는 값은 output <타입> . 하나이고, 본문에서 return <식> . 으로 돌려준다.

fn compare input a i32 . input b i32 . output i32 .
do
  return sub a. b. . .
end .

타입 앞에 붙는 낱말이 소유와 가변과 유무를 말한다. 자주 만나는 것은 다음과 같다.

적는 것뜻
input xs slice u8 .남의 바이트를 읽기로 빌린다
input xs mut slice u8 .남의 바이트에 쓸 수 있게 빌린다(proc 이어야 한다)
input p ref point . · mut_ref point값 하나를 읽기로·쓰기로 빌린다(12장)
input o option u64 .있을 수도 없을 수도 있는 값(11장)
input out cap io .권한(16장)
input comptime t type .번역할 때 정해지는 타입(22장)

표 5.1 — 매개변수 타입에 붙는 낱말

반환값은 하나다. 여럿을 돌려주려면 struct 로 묶거나(10장), 호출자가 넘긴 mut 슬라이스에 쓴다.

출력에 이름을 붙일 수도 있다 — output <이름> <타입> .. 그 이름은 몸 안의 지역이고 0 에서 시작한다. 수는 0, bool 은 false, option 은 none, 구조체는 칸마다 0 이다. 몸이 그 이름에 쓰고 return <이름> . 으로 돌려준다. 결과를 조금씩 쌓는 op 에서 «먼저 0 으로 채운 지역을 하나 만든다» 는 줄이 사라진다.

fn total input xs slice u64 . output t u64 .
do
  for x xs. do
    set t. add t. x. . .
  end .
  return t. .
end .

output 뒤의 첫 낱말이 타입 이름이면 출력 전체가 타입이고(output result u64 perr .), 타입 이름이 아니면 결과의 이름이다. 0 이 정해지지 않은 타입(슬라이스 · result · enum 같은 것)에는 이름을 붙일 수 없다(E-RESULT-NOZERO) — 그런 출력은 이름 없이 적고 값을 돌려준다. ensures 는 여전히 ret 으로 결과를 가리킨다.

배열도 돌려줄 수 있다 — output r array 3 u64 .. 배열에는 값으로 돌려주는 길이 없어서 부르는 쪽이 자리를 준다. 부르는 쪽은 결과를 이름에 묶고(let s array 3 u64 squares n. . .), 부름 받는 쪽은 그 이름의 바이트에 바로 짓는다. 그래서 식 한가운데서 부를 수는 없다(idx squares n. . 0 . 은 E-RESULT-PLACE) — 먼저 이름에 묶는다.

fn squares input n usize . output r array 3 u64 .
  requires le n. 1000 . .
do
  repeat i usize 3 do
    set idx r. i. . cast u64 mul add n. i. . add n. i. . . . .
  end .
  return r. .
end .

5.6 neg 만 단항이다#

사칙 연산 add·sub·mul·div·mod 는 인자를 둘 받는다. 산술에서 인자를 하나 받는 것은 부호를 뒤집는 neg 뿐이다.

examples/ch05/neg.low

module negs .
rem run: flip 5
rem run: dist -3 4

fn flip input a i64 . output i64 .
do
  return neg a. . .
end .

fn dist input a i64 . input b i64 . output i64 .
do
  return abs sub a. b. . . .
end .

실행 결과

$ lowentc --run flip neg.low 5
flip(5) = -5
$ lowentc --run dist neg.low -3 4
dist(-3, 4) = 7

neg 는 부호 있는 타입의 일이다. 부호 없는 값에 쓰면 넘침이다. abs 는 절댓값이고, i64 의 가장 작은 값처럼 양수로 표현할 수 없는 값을 주면 멈춘다. expr 섬 안에도 단항 연산자는 없어서, 부호를 뒤집으려면 expr 0 - a 로 적거나 (neg a) 를 괄호로 부른다.

5.7 수식어#

op 머리 앞에 수식어를 붙일 수 있다.

unsafe extern proc c_area do
  input k cap c .
  input w i64 .
  input h i64 .
  output i64 .
  effects unsafe .
  link lw_c_area .
end .

5.8 op 안의 op#

한 곳에서만 쓰는 작은 op 은 쓰는 자리 가까이에 둘 수 있다. op 본문 바로 아래에 fn·proc 을 선언하면 그 op 안에서만 보이는 로컬 op 이 된다. 이름이 짧아도 다른 op 과 부딪치지 않는다.

examples/ch05/localop.low

module localop .
rem run: count_selected [3,7,1,15] 3
rem run: parity 7

rem 쓰는 자리 가까이에 둔 보조 op --- 이 op 안에서만 보인다
export fn count_selected input xs slice u8 . input mask u8 . output u64 .
do
  fn has_bits input x u8 . input bits u8 . output bool .
  do
    return eq bit_and x. bits. . bits. . .
  end .
  return pipe xs. do
    filter has_bits with mask. .
    count .
  end . .
end .

rem 선언 앞에서 불러도 되고, 형제끼리 서로 불러도 된다
export fn parity input n u64 . output u64 .
do
  fn is_even input k u64 . output u64 .
  do
    if eq k. 0 . do
      return 1 .
    end .
    return is_odd sub k. 1 . . .
  end .
  fn is_odd input k u64 . output u64 .
  do
    if eq k. 0 . do
      return 0 .
    end .
    return is_even sub k. 1 . . .
  end .
  return is_even n. . .
end .

실행 결과

$ lowentc --run count_selected localop.low [3,7,1,15] 3
count_selected([3,7,1,15], 3) = 3
  arg0 (written) = [3,7,1,15]
$ lowentc --run parity localop.low 7
parity(7) = 0

examples/ch05/mistake_capture.low

module mistake_capture .
rem expect: E-LOCAL-CAPTURE

rem ✘ 로컬 op 은 바깥의 `limit` 을 보지 않는다 --- 매개변수로 건넨다
export fn count_over input xs slice u8 . input limit u8 . output u64 .
do
  fn over input x u8 . output bool .
  do
    return gt x. limit. . .
  end .
  return pipe xs. do
    filter over .
    count .
  end . .
end .

실행 결과

$ lowentc --check mistake_capture.low
9:18 E-LOCAL-CAPTURE: a local op does not see the enclosing op's parameters, locals, regions or borrows — it is an ordinary op declared close to its use, with no hidden capture. Pass the value in as an input (RFC-0121 §6.6)

바깥의 limit 을 쓰고 싶으면 over 에 input limit u8 . 을 더하고 pipe 에서 filter over with limit . 로 건넨다 (24장).

5.9 흔한 실수#

처음 op 을 쓸 때 누구나 한 번쯤 밟는 자리들이다. 모두 번역할 때 걸리므로, 진단 코드를 보고 이 절로 돌아오면 된다.

반례. C 처럼 괄호와 쉼표로 부른다

다른 언어에 익숙하면 손이 먼저 add3(1, 2, 3) 을 친다.

examples/ch05/mistake_ccall.low

module mistake_ccall .
rem expect: E-VOCAB-REMOVED

fn add3 input a i32 . input b i32 . input c i32 . output i32 .
do
  return add add a. b. . c. . .
end .

fn use3 output i32 .
do
  rem ✘ C 처럼 괄호와 쉼표로 불렀다 --- Lowent 의 호출은 `이름 인자 인자 …` 다
  return add3(1, 2, 3) .
end .

실행 결과

$ lowentc --check mistake_ccall.low
12:16 E-VOCAB-REMOVED: `,` (R3) was removed — RFC-0103, 2026-08-27. It opened the NEXT operand of the same form, but nothing used it that way: every `,` in the corpus was a LINE CONTINUATION, and newlines no longer close a form, so continuing a line needs nothing at all. A form is `head operand*` and ends at its closer `.` — just write the operands, on as many lines as you like.
12:19 E-VOCAB-REMOVED: `,` (R3) was removed — RFC-0103, 2026-08-27. It opened the NEXT operand of the same form, but nothing used it that way: every `,` in the corpus was a LINE CONTINUATION, and newlines no longer close a form, so continuing a line needs nothing at all. A form is `head operand*` and ends at its closer `.` — just write the operands, on as many lines as you like.

Lowent 의 호출은 이름 다음에 인자를 공백으로 늘어놓는 전위 표기다. 쉼표는 한때 쓰였다가 없앤 기호라서 E-VOCAB-REMOVED 가 나온다. 괄호·쉼표 없이 부르는 까닭은 기호를 줄여 누구나(휴대전화 자판으로도) 같은 모양으로 쓰게 하려는 것이다. 고치는 법: return add3 1 2 3 . .

반례. 인자 수가 input 절 수와 다르다

examples/ch05/mistake_arity.low

module mistake_arity .
rem expect: E-IR-ARITY

fn add3 input a i32 . input b i32 . input c i32 . output i32 .
do
  return add add a. b. . c. . .
end .

fn use3 output i32 .
do
  rem ✘ input 절이 셋인데 인자를 둘만 줬다
  return add3 1 2 . .
end .

실행 결과

$ lowentc --check mistake_arity.low
mistake_arity.low:12:0 E-IR-ARITY: `add3` takes 3 arguments but 2 are given here — an op call is `<name> <arg>…` with exactly as many arguments as its `input` clauses

input 절 하나가 인자 하나다. 기본값 인자나 인자 수가 달라지는 op 은 없다 — 머리를 보면 부르는 모양이 하나로 정해져야 읽는 사람이 헷갈리지 않기 때문이다. 진단이 몇 개를 받고 몇 개를 주었는지 말해 준다.

반례. 어느 갈래에서 return 을 빠뜨린다

examples/ch05/mistake_partial.low

module mistake_partial .
rem expect: E-RETURN-PARTIAL

fn sign input a i32 . output i32 .
do
  if gt a. 0 . do
    return 1 .
  end .
  rem ✘ a 가 0 이하일 때 돌려줄 값이 없다 --- 끝까지 내려오는 길이 있다
end .

실행 결과

$ lowentc --check mistake_partial.low
mistake_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

output 을 적은 op 은 모든 길에서 값을 돌려주어야 한다. 옛 도구는 빠진 자리에서 조용히 0 을 돌려주었는데, 그 0 은 소스 어디에도 적혀 있지 않은 값이라 버그를 숨겼다. 그래서 지금은 E-RETURN-PARTIAL 로 거절한다. 고치는 법은 if 뒤에 나머지 경우의 return 을 적는 것이다 — 예를 들면 return 0 . 을 end 앞에 둔다.

반례. 기본 연산 의 이름으로 op 을 짓는다

examples/ch05/mistake_builtin.low

module mistake_builtin .
rem expect: E-NAME-BUILTIN

rem ✘ `min` 은 이미 내장 op 의 이름이다 --- 이 선언은 영영 불리지 않는다
fn min input a u64 . input b u64 . output u64 .
do
  return add a. b. . .
end .

실행 결과

$ lowentc --check mistake_builtin.low
mistake_builtin.low:5:0 E-NAME-BUILTIN: this name is a CORE OP — the resolver always picks the core op, so your declaration can never be called: it exists and does not exist. The namespace is FLAT (no shadowing). Rename it

이름 공간은 평평해서 같은 이름이 둘일 수 없다. 기본 연산(add·min·len·ok …)의 이름을 쓰면 그 선언은 영영 불리지 않으므로 E-NAME-BUILTIN 으로 알려 준다. 지역 이름에도 같은 규칙이 붙는다. 기본 연산 이름의 목록은 부록 A 에 있다.

흔한 오해. 결과를 받지 않아도 fn 을 부르면 무언가 일어난다

fn 은 순수하므로 결과를 버리면 아무 일도 일어나지 않은 것과 같다. 컴파일러는 그런 호출을 지워도 된다.

examples/ch05/discard.low

module discard .
rem expect: E-VALUE-DISCARDED

fn twice input a u64 . output u64 .
do
  return mul a. 2 . .
end .

fn caller output u64 .
do
  rem ✘ 결과를 받지 않은 fn 호출 --- 순수하므로 아무 일도 안 한다. 그래서 거절된다
  twice 5 .
  rem 결과를 쓰려면 이름에 묶는다
  let t u64 twice 5 . .
  return t. .
end .

실행 결과

$ lowentc --check discard.low
discard.low:12:0 E-VALUE-DISCARDED: this statement computes a value and drops it — nothing changes. Did you mean `set <name> (…) .`? (A pure op or a `fn` call has no effect to be a statement for — RFC-0113 R5; to put a number into a bitset write `bitset_insert`)

첫 twice 5 . 는 계산만 하고 결과를 버린다 — 아무 일도 안 하는 문장이라 처리기가 E-VALUE-DISCARDED 로 거절한다(RFC-0113 R5). 결과를 쓰려면 둘째 줄처럼 이름에 묶는다. 무언가를 하려는 호출(출력·쓰기)이라면 그것은 proc 이어야 하고, 그러면 머리에 효과가 적힌다.

5.10 이 장의 문법을 한눈에#

모양뜻왜 이렇게
fn f input a T . output R . do … end .순수한 op결과를 기억·재배치·생략해도 되는지 머리만 보고 알게
proc f … effects E . do … end .효과를 낼 수 있는 op무엇을 하는지(E)가 머리에 드러나게
input x T .매개변수 하나절마다 하나라 이름과 타입이 줄마다 보인다
output T . · output void .돌려주는 값의 타입 · 돌려주는 값 없음반환은 하나 — 여럿이면 struct 로 묶는다
output r T .이름 붙은 결과 — 0 에서 시작하는 지역결과를 쌓는 op 에서 «0 으로 채운 지역» 줄이 사라진다
return e .값을 돌려주고 끝낸다모든 길에서 적어야 한다(E-RETURN-PARTIAL)
f a b c부르기 — 이름 뒤에 인자를 공백으로괄호·쉼표 없는 전위 표기
f g a. . b. .호출을 인자로 넣기괄호는 “이 안이 값 하나” 라는 표시
requires c .부르는 쪽이 지켜야 할 조건어긴 쪽의 잘못임을 진입에서 알린다(14장)
effects state .호출자의 저장소를 고친다mut 매개변수로 쓰는 op 의 머리
neg a. .부호 뒤집기(유일한 단항 산술)빼기와 뒤섞이지 않게 이름으로
export · extern · unsafe내보내기 · 몸이 C 에 · 검사 못 하는 일할 수 있는 일이 머리 앞에 보이게
do 바로 아래의 fn g … end .로컬 op — 그 op 안에서만 보인다바깥 이름을 보지 않는다 — 클로저가 아니다

표 5.2 — op 의 문법 — 모양 · 뜻 · 왜 이렇게 생겼나

복습 정리

op 은 fn 이거나 proc 이고 갈래는 언제나 적는다. 순수함은 관측적이어서 op 안에 갇힌 변이는 순수로 치지만, mut 매개변수로 호출자에게 보이는 쓰기는 proc 이어야 한다. fn 에는 effects 를 적지 않고, proc 의 effects 는 좁히는 절이다. 효과는 부르는 쪽으로 번진다. 매개변수는 절마다 하나, 반환은 하나이며, 산술의 단항은 neg 뿐이다.