naming: panic을 crash로 — 그리고 이름을 고르는 원칙을 철학에 넣는다
철학 6번을 추가했다: 이름은 관례가 아니라 뜻에서 고른다 — 낯섦은 한 번 치르고 끝나지만 부정확함은 읽는 사람마다 매번 치른다. 판정 방법도 같이 적었다. 그 단어로 평범한 문장을 써 보고, 단어가 문장을 도우면 맞는 이름이고 싸우면 틀린 이름이다. "크래시는 복구하는 것이 아니라 조사하는 것이다" — 돕는다 "패닉은 복구할 수 없다" — 다른 언어에서는 할 수 있어 싸운다 panic의 자연어 뜻은 "갑작스러운 공포"다. 반응하는 쪽의 감정이지 결함에 대한 말이 아니다. 그리고 Go/Rust에서는 붙잡을 수 있어 이름이 거짓말을 한다. crash는 "계획 없이 갑자기 완전히 망가져 끝남"이고 복구의 함의가 없다 — 크래시는 복구하는 게 아니라 조사하는 것이다. 어휘의 출신도 이유가 됐다. panic+recover는 Go 전통이고 거기엔 감독이 없다. crash+supervision은 얼랭 전통이며, 우리가 만드는 것이 그쪽이다. 한국어 용어도 세 층으로 정리했다: 실패(Result) / 결함(crash) / 감독. 세 층이 세 가지 다른 기제로 규율된다 — 타입, 없음(발산), capability. "상황이 나쁨"은 결함이 아니라 실패다. 이 선을 안 그으면 crash가 게으름의 배출구가 된다. "오류"는 컴파일러 진단에만 쓴다. 얼랭 질문에 대한 답도 기록했다: 감독은 가져오고 비구조적 spawn은 안 가져온다. sc.spawn과 sup.spawn(sc, f)로 갈리며 문법 변경이 없다 — 실패를 삼키려면 Supervisor를 받았어야 하고 그것이 시그니처에 보인다. 개명은 문법을 먼저 고치고 대조 장치로 확인했다. 문장 500개, 파일 26개 모두 갈림 0건. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
This commit is contained in:
+54
-12
@@ -28,6 +28,16 @@ AI의 코드 생성 속도 >> 신뢰 확보 속도.
|
||||
→ "Effects: +payment.refund" 수준으로 리뷰 압축
|
||||
5. 한 개념 = 한 방식: syntax variant 최소화, 공식 formatter 하나,
|
||||
공식 toolchain 하나. 단순성의 기준은 "작성자"가 아니라 "리뷰어와 검증기"
|
||||
6. 이름은 관례가 아니라 뜻에서 고른다 — 낯섦은 한 번 치르고 끝나지만
|
||||
부정확함은 읽는 사람마다 매번 치른다
|
||||
→ 판정: 그 단어로 그것이 무엇인지 평범한 문장을 써 보라. 단어가 문장을
|
||||
도우면 맞는 이름, 문장과 싸우면 틀린 이름이다
|
||||
"크래시는 복구하는 것이 아니라 조사하는 것이다" — 돕는다
|
||||
"패닉은 복구할 수 없다" — 다른 언어에서는 할 수 있어 싸운다
|
||||
→ 널리 쓰인다는 것은 옳다는 증거가 아니다. 운영체제의 "fault"가 그렇다 —
|
||||
잘못이 아닌 것을 잘못이라 부르고, 그 오류까지 함께 전파된다
|
||||
※ 관례가 정확하면 관례를 따른다. 관례라서가 아니라 정확해서다
|
||||
※ 이름에만 적용된다. 동작을 관례와 다르게 만들 이유는 아니다
|
||||
|
||||
■ 파생 결정
|
||||
|
||||
@@ -338,29 +348,43 @@ L2 빠른 테스트 / L3 fuzzing / L4 formal proof → 요청 시, 분리 실행
|
||||
invalidation 범위를 넓히는가? / unrelated code 의미를 바꾸는가? /
|
||||
기존 개념의 중복 표현인가? → 강한 이유 없으면 거절
|
||||
|
||||
■ 복구 불가능한 실패 — panic
|
||||
panic(message) -> Never. 키워드다 (prelude가 없어 함수로 두면 매번 import).
|
||||
- effect가 아니다. 경계 검사 하나에 {Panic}이 호출자 전부로 전염되면
|
||||
■ 실패의 세 층 — 실패 / 결함 / 감독
|
||||
서로 다른 세 질문에 답한다. 아래 층은 위 층 없이도 성립한다.
|
||||
|
||||
실패 (Result) 호출자가 대처할 수 있는가? 타입이 강제한다
|
||||
결함 (crash) 이 태스크가 계속할 수 있는가? 컴파일러는 추적하지 않는다
|
||||
감독 죽은 태스크를 어떻게 다루는가? capability가 강제한다
|
||||
|
||||
세 층이 세 가지 다른 기제로 규율된다는 것이 제대로 나뉘었다는 증거다.
|
||||
"상황이 나쁨"은 결함이 아니라 실패다 — 디스크가 가득 찬 것은 Result다.
|
||||
이 선을 긋지 않으면 crash가 게으름의 배출구가 된다.
|
||||
※ 취소는 이 셋 중 어디에도 없다. 취소는 실패가 아니라 "더 이상 필요 없음"
|
||||
이며 별도의 결정으로 남는다.
|
||||
"오류"는 컴파일러가 내는 진단에만 쓴다.
|
||||
|
||||
■ 결함 — crash
|
||||
crash(message) -> Never. 키워드다 (prelude가 없어 함수로 두면 매번 import).
|
||||
- effect가 아니다. 경계 검사 하나에 {Crash}가 호출자 전부로 전염되면
|
||||
effect 절은 신호가 아니라 잡음이 된다. 발산이 effect가 아닌 것과 같은
|
||||
이유다 — 무한 루프도 추적하지 않는다.
|
||||
- Never는 어떤 타입 자리에도 놓인다. 그래야 match 팔에서 쓸 수 있고,
|
||||
그게 없으면 panic은 식 자리에서 못 쓴다.
|
||||
그게 없으면 crash는 식 자리에서 못 쓴다.
|
||||
- 언어 수준 recover가 없다. 붙잡는 것이 있으면 그것은 예외이고, 예외는
|
||||
시그니처에 안 적히므로 철학 1과 충돌한다.
|
||||
- 되감기를 하지 않는다. ※ panic 시 자원 해제 여부는 자원 모델과 함께
|
||||
- 되감기를 하지 않는다. ※ crash 시 자원 해제 여부는 자원 모델과 함께
|
||||
결정한다 — 지금은 열어둔다.
|
||||
- 0으로 나누기, assert 실패, 미래의 범위·오버플로가 전부 이 하나로 모인다.
|
||||
※ panic은 프로그램의 버그를 말한다. 예상되는 실패는 Result다. 호출자가
|
||||
대처할 수 있는 것을 panic으로 처리하면 오용이다.
|
||||
※ crash는 프로그램의 결함을 말한다. 예상되는 실패는 Result다. 호출자가
|
||||
대처할 수 있는 것을 crash로 처리하면 오용이다.
|
||||
|
||||
■ panic은 어디까지 올라가는가 (실패의 단위)
|
||||
■ crash는 어디까지 올라가는가 (실패의 단위)
|
||||
콜 스택의 root가 아니라 scope 트리의 root다. 즉 실패의 단위는 프로세스가
|
||||
아니라 태스크다. scope가 이미 그 모양이기 때문이다 — 렉시컬이고, 블록을
|
||||
나가는 것이 join이며, 자식이 죽었다는 사실이 부모에게 도달하는 지점이
|
||||
문법에 이미 있다(닫는 중괄호).
|
||||
|
||||
panic은 scope 트리를 타고 올라간다. 자식이 panic하면 그 scope는 join
|
||||
지점에서 panic한다. root까지 도달하면 실행이 끝난다.
|
||||
crash는 scope 트리를 타고 올라간다. 자식이 crash하면 그 scope는 join
|
||||
지점에서 crash한다. root까지 도달하면 실행이 끝난다.
|
||||
|
||||
v0는 태스크가 하나뿐이라 이 규칙이 관측되지 않는다 — "태스크가 죽는다"와
|
||||
"프로그램이 죽는다"가 같은 사건이다. 그래도 지금 적는 이유는, 동시성이
|
||||
@@ -369,9 +393,27 @@ v0는 태스크가 하나뿐이라 이 규칙이 관측되지 않는다 — "태
|
||||
전파는 취소를 정하지 않고도 정의된다. 취소가 생기면 형제들이 언제 멈추는지가
|
||||
바뀔 뿐, scope가 실패한다는 사실은 바뀌지 않는다.
|
||||
※ 형제 취소는 여전히 미정이다. 대가를 알고 미룬다 — 취소가 없으면 형제
|
||||
하나가 끝나지 않을 때 죽은 자식의 panic이 join에 도달하지 못한다.
|
||||
하나가 끝나지 않을 때 죽은 자식의 crash가 join에 도달하지 못한다.
|
||||
실패가 hang에 가려진다. 이것이 취소가 필요한 이유이기도 하다.
|
||||
|
||||
■ 얼랭 방식은 되는가 — 감독은 되고 비구조적 spawn은 안 된다
|
||||
공유 가변 상태 없음, let it crash, 죽은 쪽의 상태를 물려받지 않음 —
|
||||
얼랭과 이미 같다. 다른 경로로 같은 결론에 도착했다.
|
||||
가져오지 않는 것: 얼랭의 비구조적 spawn(Pid가 부모보다 오래 산다).
|
||||
부모 없는 태스크는 ambient authority와 같은 문제이고, 구조적 동시성을 고른
|
||||
이유가 그것이다.
|
||||
가져오는 것: 감독. scope 트리가 이미 supervision 트리 모양이다.
|
||||
sc.spawn(f) 실패가 부모로 전파된다
|
||||
sup.spawn(sc, f) 실패가 여기서 멈춘다
|
||||
spawn은 하나의 개념이고 실패가 어디까지 가느냐가 권한으로 갈린다 — 실패를
|
||||
삼키려면 Supervisor를 받았어야 하고 그것이 시그니처에 보인다. 문법 변경 없음.
|
||||
재시작 가능한 자식은 copyable 클로저여야 한다(fn vs affine fn). 얼랭이 관례로
|
||||
지키는 것을 affinity가 이미 검사한다. 재시작 전략은 라이브러리다(얼랭도 OTP).
|
||||
※ 전제조건은 자원 모델이다. 죽은 태스크가 잡았던 것이 정리되지 않으면
|
||||
재시작이 곧 누수다.
|
||||
※ 얼랭의 보장은 BEAM의 선점 스케줄링과 프로세스별 힙 위에 선다. 우리에겐
|
||||
아직 둘 다 없다 — 이것은 설계 층의 답이지 v0가 보여줄 수 있는 것이 아니다.
|
||||
|
||||
■ 격리 경계는 recover가 아니다
|
||||
런타임은 격리 경계를 가질 수 있다. 테스트 러너가 첫 사례이고, 서버의
|
||||
요청 경계가 두 번째가 될 것이다. 구분선은 이것이다:
|
||||
@@ -397,7 +439,7 @@ capability를 만드는 문법도 없다. 따라서 effect-free임이 증명된
|
||||
interface hash에 들어가지 않는다 — 테스트를 고쳤다고 downstream이
|
||||
재검사되면 안 된다.
|
||||
컴파일 타임 메타프로그래밍 없음. assert는 std/test.cool에 coollang으로
|
||||
쓰인다 (panic 위의 설탕) — std에서 본문이 있는 첫 함수다. 실패 메시지에
|
||||
쓰인다 (crash 위의 설탕) — std에서 본문이 있는 첫 함수다. 실패 메시지에
|
||||
값이 안 나오는 것은 의도다. 표현식 텍스트를 잡으려면 매크로가 필요하다.
|
||||
※ effect 있는 코드는 테스트할 수 없다. 가짜 capability를 만드는 수단이
|
||||
없기 때문이다. 실제로 불편해진 뒤에 판단한다.
|
||||
|
||||
Reference in New Issue
Block a user