app: 개밥 먹기 — 일하는 프로그램 하나와 그 마찰 보고

1단계 최소 IO: File(읽기), Args capability. 권한의 출처는 여전히 런타임
하나이고, IO 오류는 Result[a, String]이다 — 런타임이 사용자 정의 enum을
만들 수 없고, 만들 수 있게 하면 런타임이 프로그램의 타입을 알아야 한다.

2단계 std 확장: fold, filter, push, concat, reverse, is_empty,
String.split/trim/starts_with/contains, Int.parse.

3단계 samples/app: 설정 파서 + 리포트 도구, 2모듈 304줄. 검사기를 시험
하려고 쓴 것이 아니라 일을 하려고 쓴 첫 프로그램이다.

산출물은 프로그램이 아니라 docs/friction.md다. 요약:
- 되돌리기 비싼 결정은 하나도 후회되지 않았다. capability 전달, effect
  명시, 실패를 버릴 수 없음, 소진적 match — 300줄 내내 거추장스럽지
  않았고 소진성은 실제로 실수를 잡았다(Value에 경우 하나 추가하니 고칠
  자리 넷을 정확히 짚었다).
- 불편은 전부 되돌리기 싼 것들이었다. 리스트 n번째 접근이 없어 fold로
  우회(40줄), else if가 없어 3~4단 중첩, String.concat이 2항이라 중첩
  지옥. 304줄 중 70줄쯤이 이 셋 때문에 존재한다.

v0의 질문은 "되돌리기 비싼 결정이 옳은가"였고 답은 그렇다이다.

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 16:06:08 +09:00
co-authored by Claude Opus 5
parent 5ac899df68
commit 5831af7760
14 changed files with 703 additions and 9 deletions
+4 -1
View File
@@ -3,4 +3,7 @@
(libraries coollang)
(deps
(glob_files %{workspace_root}/samples/*.cool)
(glob_files %{workspace_root}/std/*.cool)))
(glob_files %{workspace_root}/std/*.cool)
(glob_files %{workspace_root}/samples/app/*)
(glob_files %{workspace_root}/samples/modules/*.cool)
(glob_files %{workspace_root}/samples/run/*.cool)))
+39
View File
@@ -1232,3 +1232,42 @@ let () =
in
List.exists (fun m -> has_sub m "가져왔지만 쓰지 않습니다") ms
&& List.exists (fun m -> has_sub m "String이(가) 필요한데 Int") ms)
(* ------------------------------------------------------------------ *)
(* 실제 프로그램 *)
(* *)
(* samples/app은 검사기를 시험하려고 쓴 것이 아니라 일을 하려고 쓴 것이다. *)
(* 언어가 쓸 만한지는 이런 코드에서만 드러난다. *)
(* ------------------------------------------------------------------ *)
let () =
let st = Session.create ~root:"../samples/app" ~std:"../std" () in
match Session.run ~args:[ "../samples/app/example.conf" ] st
"../samples/app/main.cool"
with
| Error e ->
Printf.printf " (실행 오류: %s)\n" (Session.string_of_error e);
check "app: 설정 리포트가 돈다" false
| Ok out ->
check "app: 항목과 문제를 센다"
(has_sub out "항목 5개, 문제 2개");
check "app: 값의 타입을 모양으로 정한다"
(has_sub out "threads = 4 (number)"
&& has_sub out "verbose = true (flag)"
&& has_sub out "name = \"coollang\" (text)");
check "app: 문제에 줄 번호가 붙는다"
(has_sub out "10행: 이름이 비어 있습니다"
&& has_sub out "11행: = 가 하나여야 합니다");
check "app: 타입 있는 조회" (has_sub out "port = 8080")
let () =
let st = Session.create ~root:"../samples/app" ~std:"../std" () in
match Session.run st "../samples/app/main.cool" with
| Ok out -> check "app: 인자가 없으면 말해준다" (has_sub out "경로가 필요합니다")
| Error _ -> check "app: 인자가 없으면 말해준다" false
let () =
let st = Session.create ~root:"../samples/app" ~std:"../std" () in
match Session.run ~args:[ "/없는/파일.conf" ] st "../samples/app/main.cool" with
| Ok out -> check "app: 없는 파일을 Err로 돌려준다" (has_sub out "오류: ")
| Error _ -> check "app: 없는 파일을 Err로 돌려준다" false