파일과 풀은 정리 지점이 렉시컬이었다. LRU는 아니다 — 축출은 삽입의 부작용으로
예측할 수 없는 때에 일어난다. with를 구현하기 전에 알아야 할 자리였다.
D10 (큰 것) — Option/Result 패턴이 타입을 통째로 잃고 있었다.
match r { Ok(v) => v, Err(e) => e } // Int를 String 자리에 두는데 통과
typecheck의 env.ctors에 내장 생성자가 없어서 None이 "None이라는 이름의 변수
바인딩"이 되고 Ok(v)의 v가 TUnknown이 됐다. 즉 이 언어의 핵심 오류 처리
수단의 패턴 매칭이 타입을 전혀 검사하지 않고 있었다. 소진성 검사는 자기
is_ctor에 내장을 갖고 있어 이 사실을 덮었다 — 빠진 경우는 잡으면서 타입은
안 봤다. 내장 열거형을 등록해 고쳤고 기존 코드는 하나도 안 깨졌다.
여태 안 보인 이유: Ok(v) => Int.show(v) 같은 정상 코드는 v가 TUnknown이어도
통과하므로 아무도 이상함을 못 느낀다. 틀린 코드를 써봐야 드러난다.
D11 — struct 필드에서 affine 값을 꺼낼 수 없다. 필드 접근은 빌림이고 struct
분해 패턴이 없다. 열거형은 패턴이 분해하므로 가능하다. 즉 "둘을 함께
돌려주기"가 열거형으로만 된다. 튜플이 없는 대가가 표현 가능성 문제로 나왔다.
D12 — 축출은 렉시컬이 아니다. 그래서 자원이 렉시컬 전용이면 자원 컨테이너를
언어로 만들 수 없다. 이미 그렇게 되어 있었다 — std-draft의 Pool이 불투명한
런타임 capability인 이유가 만들 수 없어서였다. (a) 렉시컬 전용을 택하고,
사용자가 자기 자원 풀을 못 만든다는 대가를 기록한다.
자료구조 자체의 마찰: Map이 없어 O(n), 튜플이 없어 운반용 struct 세 개,
get이 캐시를 새로 돌려줘야 함, 뒤에서 자르는 std 함수 없음.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
74 lines
2.3 KiB
Plaintext
74 lines
2.3 KiB
Plaintext
// LRU 캐시 — 자원 판.
|
|
//
|
|
// 앞의 두 개밥(파일, 풀)은 정리 지점이 렉시컬이었다. 여기는 아니다.
|
|
// 축출은 삽입의 부작용으로, 예측할 수 없는 때에 일어난다.
|
|
//
|
|
// 이것이 with 결정 전에 알아야 할 자리다 — with는 렉시컬 수명만 다루는데
|
|
// 축출된 자원은 어느 블록에도 묶이지 않는다.
|
|
|
|
import "cool.dev/std/list" as List
|
|
import "../std-draft/db" as Db
|
|
|
|
pub struct Held {
|
|
key: String,
|
|
lease: Db.Lease,
|
|
}
|
|
|
|
// Lease를 필드로 가지므로 전이적으로 affine이다 — 선언하지 않아도 유도된다.
|
|
pub struct LeaseCache {
|
|
cap: Int,
|
|
held: List[Held],
|
|
}
|
|
|
|
// put은 캐시와 "떨어져 나온 자원"을 함께 돌려줘야 한다. 튜플이 없다.
|
|
//
|
|
// struct로 쓰면 막힌다 — 필드 접근은 빌림이라 affine 값을 꺼낼 수 없고,
|
|
// struct를 분해하는 패턴이 언어에 없다. 열거형은 패턴이 분해하므로 꺼낼 수
|
|
// 있다. 그래서 "둘을 함께 돌려주기"가 열거형으로만 가능하다.
|
|
pub enum Put {
|
|
Kept(LeaseCache),
|
|
Evicted(LeaseCache, Db.Lease),
|
|
}
|
|
|
|
pub fn empty(cap: Int) -> LeaseCache {
|
|
LeaseCache { cap: cap, held: [] }
|
|
}
|
|
|
|
// 넣는다. 넘치면 가장 오래된 것이 떨어져 나온다.
|
|
//
|
|
// 떨어져 나온 lease는 호출자가 반납해야 한다. 언어는 그것을 강제하지 못한다 —
|
|
// Put.evicted를 무시하고 버려도 아무 말이 없다. 누수 허용이므로.
|
|
pub fn put(own c: LeaseCache, key: String, own l: Db.Lease) -> Put
|
|
|
|
// 호출자가 해야 하는 일. 이 함수를 안 부르면 조용히 샌다.
|
|
pub fn drain(db: Db.Db, pool: Db.Pool, own p: Put)
|
|
effects {Db.release} -> LeaseCache {
|
|
match p {
|
|
Kept(c) => c,
|
|
Evicted(c, l) => release_then(db, pool, l, c),
|
|
}
|
|
}
|
|
|
|
pub fn release_then(
|
|
db: Db.Db,
|
|
pool: Db.Pool,
|
|
own l: Db.Lease,
|
|
own c: LeaseCache,
|
|
) effects {Db.release} -> LeaseCache {
|
|
let _ = db.release(pool, l)
|
|
c
|
|
}
|
|
|
|
// 쓰는 쪽. 매 삽입마다 축출을 받아 처리해야 한다.
|
|
pub fn add(db: Db.Db, pool: Db.Pool, own c: LeaseCache, key: String)
|
|
effects {Db.acquire, Db.release} -> Result[LeaseCache, Db.DbError] {
|
|
match db.acquire(pool) {
|
|
Err(e) => Err(e),
|
|
Ok(l) => Ok(drain(db, pool, put(c, key, l))),
|
|
}
|
|
}
|
|
|
|
pub fn size(c: LeaseCache) -> Int {
|
|
List.len(c.held)
|
|
}
|