effects: effect/capability 검사
문서가 "사활"이라고 지목한 단계다. effects 절이 여기서부터 장식이 아니라 검사 대상이 된다. 핵심 규칙 — 미선언 effect = compile error(철학 1). 수행한 자리를 들고 다니므로 진단이 함수 머리가 아니라 실제로 수행한 줄에 붙는다. effect가 흐르는 경로 넷을 모두 막았다: - capability 메서드 호출이 그 메서드의 선언된 effect를 요구한다 - 함수 호출이 그 함수의 effect를 물려준다 - 클로저의 effect는 정의한 자리가 아니라 부르는 자리에서 일어난다. 클로저를 만들기만 하는 것은 effect가 아니고, 인자로 넘겨 호출되는 순간 호출자의 것이 된다 - 파라미터가 허용한 범위를 넘는 함수를 넘기면 거부한다 effect 변수는 결정 위치에서 인자의 effect로 묶인다. 순서가 중요해서 한 번 틀렸다 — 포함 검사를 unify보다 먼저 하면 아직 해소되지 않은 미지수를 제약으로 오해해 정당한 코드를 거부한다. unify가 먼저고, 남는 차이만이 위반이다. 결정되지 않은 미지수는 판정을 미룬다 — 모르는 것을 위반이라고 말하지 않는다. 보안 정리 (i)을 직접 구현했다: capability 메서드는 값을 통해서만 부를 수 있다. 타입 이름으로 부를 수 있으면 capability 없이 effect를 수행하게 되어 정리가 무너진다. effect 검사는 타입 검사와 같은 순회에서 돈다. effect 변수의 해소가 타입 변수와 같은 지점에서 일어나므로 떼어내면 순회와 인스턴스화를 두 번 한다. 소유는 나뉘되 순회는 하나다 — 문서에 근거를 적었다. 10_effect_errors.cool 추가. capability를 직접 정의해야 메서드의 effect가 알려지므로 외부 타입을 하나도 쓰지 않는다. 통과해야 하는 6개와 거부해야 하는 7개가 모두 의도대로 갈린다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
+94
-3
@@ -580,7 +580,9 @@ let () =
|
||||
Sys.readdir dir |> Array.to_list
|
||||
|> List.filter (fun f -> Filename.check_suffix f ".cool")
|
||||
|> List.filter (fun f ->
|
||||
f <> "08_syntax_errors.cool" && f <> "09_type_errors.cool")
|
||||
f <> "08_syntax_errors.cool"
|
||||
&& f <> "09_type_errors.cool"
|
||||
&& f <> "10_effect_errors.cool")
|
||||
|> List.sort compare
|
||||
in
|
||||
List.iter
|
||||
@@ -593,7 +595,96 @@ let () =
|
||||
errors;
|
||||
check (f ^ " 타입 검사") false)
|
||||
ok_files;
|
||||
match Driver.typecheck (Filename.concat dir "09_type_errors.cool") with
|
||||
(match Driver.typecheck (Filename.concat dir "09_type_errors.cool") with
|
||||
| Ok () -> check "09는 타입 오류를 내야 한다" false
|
||||
| Error errors ->
|
||||
check "09의 오류를 전부 모은다 (첫 오류에서 멈추지 않는다)" (List.length errors >= 18)
|
||||
check "09의 오류를 전부 모은다 (첫 오류에서 멈추지 않는다)" (List.length errors >= 18));
|
||||
match Driver.typecheck (Filename.concat dir "10_effect_errors.cool") with
|
||||
| Ok () -> check "10은 effect 오류를 내야 한다" false
|
||||
| Error errors -> check "10의 effect 오류" (List.length errors >= 6)
|
||||
|
||||
(* ================================================================== *)
|
||||
(* effect / capability 검사 *)
|
||||
(* ================================================================== *)
|
||||
|
||||
let cap =
|
||||
"capability Db {\n\
|
||||
\ fn read(id: Int) effects {Db.read} -> Int\n\
|
||||
\ fn touch(id: Int) effects {Db.read}\n\
|
||||
}\n"
|
||||
|
||||
(* --- 미선언 effect = compile error (철학 1) --- *)
|
||||
|
||||
let () =
|
||||
check "선언하면 통과"
|
||||
(type_ok (cap ^ "fn f(db: Db) effects {Db.read} -> Int {\n db.read(1)\n}"));
|
||||
check "선언 없이 capability 메서드를 부르면 오류"
|
||||
(type_has
|
||||
(cap ^ "fn f(db: Db) -> Int {\n db.read(1)\n}")
|
||||
"선언되지 않은 effect Db.read");
|
||||
check "헬퍼의 effect도 물려받는다"
|
||||
(type_has
|
||||
(cap
|
||||
^ "fn g(db: Db) effects {Db.read} -> Int {\n\
|
||||
\ db.read(1)\n\
|
||||
}\n\
|
||||
fn f(db: Db) -> Int {\n\
|
||||
\ g(db)\n\
|
||||
}")
|
||||
"선언되지 않은 effect Db.read");
|
||||
check "effect 없는 함수는 절이 없어도 된다"
|
||||
(type_ok "fn add(a: Int, b: Int) -> Int {\n a + b\n}")
|
||||
|
||||
(* --- 클로저의 effect는 정의한 자리가 아니라 부르는 자리에서 일어난다 --- *)
|
||||
|
||||
let () =
|
||||
check "클로저를 만들기만 하면 effect가 새지 않는다"
|
||||
(type_ok
|
||||
(cap
|
||||
^ "fn f(db: Db) -> fn() effects {Db.read} -> Int {\n\
|
||||
\ fn() { db.read(1) }\n\
|
||||
}"));
|
||||
check "클로저가 선언한 것보다 많이 수행하면 오류"
|
||||
(type_has
|
||||
(cap
|
||||
^ "fn run(f: fn() effects {}) \n\
|
||||
fn f(db: Db) {\n\
|
||||
\ run(fn() effects {} { db.touch(1) })\n\
|
||||
}")
|
||||
"클로저가 선언하지 않은 effect Db.read");
|
||||
check "파라미터가 허용한 범위를 넘는 함수를 넘기면 오류"
|
||||
(type_has
|
||||
(cap
|
||||
^ "fn run(f: fn() effects {})\n\
|
||||
fn f(db: Db) {\n\
|
||||
\ run(fn() { db.touch(1) })\n\
|
||||
}")
|
||||
"파라미터가 허용한 effect는 {}")
|
||||
|
||||
(* --- effect 변수: 결정 위치에서 인자의 effect로 묶인다 --- *)
|
||||
|
||||
let () =
|
||||
let twice = "fn twice[e: effects](f: fn() effects e) effects e\n" in
|
||||
check "effect 변수는 인자의 effect로 해소된다"
|
||||
(type_ok
|
||||
(cap ^ twice
|
||||
^ "fn f(db: Db) effects {Db.read} {\n twice(fn() { db.touch(1) })\n}"));
|
||||
check "해소된 effect가 선언에 없으면 오류"
|
||||
(type_has
|
||||
(cap ^ twice ^ "fn f(db: Db) {\n twice(fn() { db.touch(1) })\n}")
|
||||
"선언되지 않은 effect Db.read");
|
||||
check "effect 변수를 그대로 물려주는 것은 통과"
|
||||
(type_ok
|
||||
(twice ^ "fn g[e: effects](f: fn() effects e) effects e {\n twice(f)\n}"));
|
||||
check "effect 변수를 선언하지 않고 물려주면 오류"
|
||||
(type_has
|
||||
(twice ^ "fn g[e: effects](f: fn() effects e) {\n twice(f)\n}")
|
||||
"선언되지 않은 effect e")
|
||||
|
||||
(* --- capability 없이는 effect를 수행할 수 없다 --- *)
|
||||
|
||||
let () =
|
||||
check "capability 값이 없으면 메서드를 부를 수 없다"
|
||||
(type_has
|
||||
(cap ^ "fn f() effects {Db.read} -> Int {\n Db.read(1)\n}")
|
||||
"값을 통해서만")
|
||||
|
||||
Reference in New Issue
Block a user