정리에 바깥 권한이 따라붙으면 with가 자원만 보고 무엇을 부를지 알 수 없다 (D9). own self로 자원이 자기 정리를 스스로 선언한다. - 문법: cap_method의 첫 자리에만 온다. 좌인수분해로 LL(1) 유지 (충돌 0건) - 빌림에는 적을 말이 없다 — self만 적는 문장은 존재하지 않는다 - own self는 interface hash에 들어간다 (호출 측 move 결과가 바뀌므로) 같이 막은 구멍 둘: - 중첩 수신자(h.lease.release())가 시그니처를 못 찾아 조용히 빌림이 됐다. own self가 생기면 그것은 자원을 두 번 소비할 수 있다는 뜻이다 - 메서드 이름만으로 시그니처를 찾아 이름이 겹치면 진단이 틀렸다. 수신자의 선언된 타입을 알면 그 capability 안에서만 찾는다 그리고 테스트 하네스의 결함: 요약과 exit 1이 파일 184줄에 있었다. 최상위 let ()은 파일 순서대로 돌므로 그 뒤 1500줄의 검사는 FAIL을 찍고도 종료 코드가 0이었다 — 실패한 테스트를 커밋할 수 있었다. 요약을 파일 끝으로 옮겼다. 개밥 먹기 네 파일을 2판으로 다시 썼다. 도우미의 시그니처가 정리의 진짜 의존을 말하게 됐다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
70 lines
2.3 KiB
Plaintext
70 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(own p: Put) effects {Lease.release} -> LeaseCache {
|
|
match p {
|
|
Kept(c) => c,
|
|
Evicted(c, l) => release_then(l, c),
|
|
}
|
|
}
|
|
|
|
// ※ 2판. `own self` 뒤로 db도 pool도 필요 없다 — 반납은 lease 자신이 안다.
|
|
pub fn release_then(own l: Db.Lease, own c: LeaseCache)
|
|
effects {Lease.release} -> LeaseCache {
|
|
let _ = l.release()
|
|
c
|
|
}
|
|
|
|
// 쓰는 쪽. 매 삽입마다 축출을 받아 처리해야 한다.
|
|
pub fn add(pool: Db.Pool, own c: LeaseCache, key: String)
|
|
effects {Pool.acquire, Lease.release} -> Result[LeaseCache, Db.DbError] {
|
|
match pool.acquire() {
|
|
Err(e) => Err(e),
|
|
Ok(l) => Ok(drain(put(c, key, l))),
|
|
}
|
|
}
|
|
|
|
pub fn size(c: LeaseCache) -> Int {
|
|
List.len(c.held)
|
|
}
|