std: 표준 라이브러리 — effect 다형성이 처음으로 검사된다

std/list.cool, string.cool, int.cool, bool.cool. 본문 없는 선언이고
런타임이 구현한다. 이 파일들은 구현이 아니라 시험대다.

부채 상환이 아니라 검증이다. std가 없을 때 List.each는 모르는 이름이라
조용히 통과했다. "모르는 것을 틀렸다고 말하지 않는다"는 맞는 원칙이지만,
그 그늘에 검사되지 않는 영역이 숨어 있었다.

넣자마자 샘플 01이 깨졌다 — List.each에 Result를 반환하는 클로저를 넘기고
그 안에서 ?를 쓰고 있었다. each는 값을 남기지 않는 클로저만 받고, ?는
클로저 밖으로 나가지 못하며, 결과를 버릴 방법은 언어에 없다. map으로
고쳤다. 이것이 std를 먼저 한 이유 그 자체다.

- IR은 이제 모듈 그래프 전체를 받고 전역 이름은 "<경로>#<이름>"으로
  정규화된다. 별칭은 가져오는 쪽의 선택이므로 실행 의미에 남아서는 안 된다.
- 본문 없는 선언은 런타임 구현으로 낮아지고, 그 이름은 모듈 파일에서 온다
  (std/list.cool의 each = "list.each"). 별칭과 무관하다.
- prelude 없음. std도 명시적으로 가져온다.
- samples/12: effect 변수가 호출 지점에서 실제로 해소된다는 증거.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
2026-08-30 15:47:44 +09:00
co-authored by Claude Opus 5
parent 1c4f46e5e5
commit 5593b54772
15 changed files with 386 additions and 108 deletions
+55 -2
View File
@@ -999,11 +999,16 @@ let run_src src =
ignore (Sys.command (Printf.sprintf "mkdir -p %s" (Filename.quote dir)));
let f = Filename.concat dir "m.cool" in
write f src;
let st = Session.create ~root:dir () in
(* 표준 라이브러리는 저장소의 std/. 테스트는 /tmp에서 도니 명시한다. *)
let st = Session.create ~root:dir ~std:"../std" () in
Session.run st f
(* prelude가 없다. 표준 라이브러리도 명시적으로 가져온다. *)
let console =
"pub capability Console {\n\
"import \"cool.dev/std/list\" as List\n\
import \"cool.dev/std/string\" as String\n\
import \"cool.dev/std/int\" as Int\n\n\
pub capability Console {\n\
\ fn print(s: String) effects {Console.print}\n\
}\n\n"
@@ -1101,3 +1106,51 @@ let () =
with
| Error e -> has_sub e.message "제공하지 않습니다"
| Ok _ -> false)
(* 표준 라이브러리 시그니처가 실제 검사에 쓰이는지.
std가 생기기 전에는 List.each가 모르는 이름이라 조용히 통과했다.
effect 다형성이 장식이 아니라는 것을 여기서 고정한다. *)
let () =
let std_check src =
let dir = Filename.concat (Filename.get_temp_dir_name ()) "cool_stdtest" in
ignore (Sys.command (Printf.sprintf "mkdir -p %s" (Filename.quote dir)));
let f = Filename.concat dir "m.cool" in
write f src;
let st = Session.create ~root:dir ~std:"../std" () in
Session.load st f;
List.map (fun (e : Session.error) -> e.message) (Session.errors st)
in
let hdr =
"import \"cool.dev/std/list\" as List\n\
import \"cool.dev/std/int\" as Int\n\n\
pub capability Console {\n\
\ fn print(s: String) effects {Console.print}\n\
}\n\n"
in
check "std 시그니처로 인자 개수를 잡는다"
(List.exists
(fun m -> has_sub m "인자 1개가 필요한데")
(std_check
(hdr ^ "pub fn f(xs: List[Int]) -> Int {\n List.len(xs, 1)\n}")));
check "std 시그니처로 반환 타입을 잡는다"
(List.exists
(fun m -> has_sub m "String이(가) 필요한데 Int")
(std_check
(hdr ^ "pub fn f(xs: List[Int]) -> String {\n List.len(xs)\n}")));
(* effect 변수가 호출 지점에서 실제로 해소된다 *)
check "List.each의 effect 변수가 클로저의 effect로 묶인다"
(List.exists
(fun m -> has_sub m "선언되지 않은 effect Console.print")
(std_check
(hdr
^ "pub fn f(c: Console, xs: List[Int]) {\n\
\ List.each(xs, fn(n) { c.print(Int.show(n)) })\n\
}")));
check "effect를 선언하면 같은 코드가 통과한다"
(std_check
(hdr
^ "pub fn f(c: Console, xs: List[Int]) effects {Console.print} {\n\
\ List.each(xs, fn(n) { c.print(Int.show(n)) })\n\
}")
= [])