"매 unsafe input 매 한번 parse → 매 typed value 매 produce, 매 downstream 매 다시 검증 의 X". 매 2019 Alexis King (lexi-lambda) 의 Haskell post 의 origin. 매 핵심 idea: 매 validation 매 boolean 의 throw — 매 information 의 lose. 매 parsing 매 validated 형식 의 새 type 의 produce — 매 type system 의 매 invariant 의 carry.
매 핵심
매 validate 의 problem
// 매 anti-pattern
functionisNonEmpty<T>(arr: T[]):boolean{returnarr.length>0;}functionhead<T>(arr: T[]):T{if(!isNonEmpty(arr))thrownewError("empty!");returnarr[0];// 매 type system 매 still T | undefined
}
매 isNonEmpty check 후 매 type 매 T[] (그대로) — 매 information lost.
매 head 매 매번 다시 check or throw.
매 caller 매 invariant 의 untracked.
매 parse 의 solution
typeNonEmpty<T>=readonly[T,...T[]];functionparseNonEmpty<T>(arr: T[]):NonEmpty<T>|null{returnarr.length>0?(arrasNonEmpty<T>):null;}functionhead<T>(arr: NonEmpty<T>):T{returnarr[0];// 매 항상 safe — type system 의 guarantee
}
매 parse 결과 매 새 type — 매 invariant 가 매 type 에 baked in.
매 downstream 매 trust — 매 re-check 의 X.
매 응용
API request body validation (Zod / Effect Schema).
typeBrand<T,B>=T&{readonly__brand: B};typeEmail=Brand<string,'Email'>;functionparseEmail(s: string):Email|null{return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s)?(sasEmail):null;}functionsendMail(to: Email,body: string){/* 매 trust to */}constraw="user@example.com";constemail=parseEmail(raw);if(!email)thrownewError("bad email");sendMail(email,"hi");// 매 OK
sendMail(raw,"hi");// 매 type error
2. Zod schema (parse-style)
import{z}from'zod';constUserSchema=z.object({id: z.string().uuid(),email: z.string().email(),age: z.number().int().min(0).max(150),});typeUser=z.infer<typeofUserSchema>;// 매 fully-typed
app.post('/users',(req,res)=>{constresult=UserSchema.safeParse(req.body);if(!result.success)returnres.status(400).json(result.error);createUser(result.data);// 매 trust — User type 매 guaranteed
});
3. Effect Schema (2026 의 매 modern)
import{SchemaasS}from"effect";constPositiveInt=S.Int.pipe(S.positive(),S.brand("PositiveInt"));typePositiveInt=S.Schema.Type<typeofPositiveInt>;constdecode=S.decodeUnknownSync(PositiveInt);constx=decode(42);// 매 PositiveInt
consty=decode(-1);// 매 throws ParseError
4. Smart constructor (Haskell-style)
classNonEmptyList<T>{privateconstructor(publicreadonlyitems: readonlyT[]){}staticparse<T>(items: readonlyT[]):NonEmptyList<T>|null{returnitems.length>0?newNonEmptyList(items):null;}gethead():T{returnthis.items[0];}// 매 always safe
gettail():readonlyT[]{returnthis.items.slice(1);}}
5. Discriminated ID type
typeUserId=Brand<string,'UserId'>;typeOrderId=Brand<string,'OrderId'>;functiongetUser(id: UserId):User{/*...*/}functiongetOrder(id: OrderId):Order{/*...*/}constuid=parseUserId(req.params.id);if(!uid)thrownewError();getUser(uid);// 매 OK
getOrder(uid);// 매 type error 매 prevent mix-up
6. Parse at boundary, trust within
// 매 boundary (HTTP / DB / file IO)
constuserOrErr=UserSchema.safeParse(rawJson);// 매 internal — User type 매 항상 valid
functionprocessUser(u: User){// 매 u.email 매 valid email — 매 re-check 의 X
// 매 u.age 매 0-150 매 — 매 re-check 의 X
}
언제: 매 API server / public library 의 매 input validation. 매 ID mix-up bug 매 routine 한 codebase. 매 domain rule 매 type-encode 가능 한 경우.
언제 X: 매 internal-only quick script. 매 highly dynamic JSON 의 schema 가 unknown. 매 perf-critical hot loop (parse overhead).
❌ 안티패턴
Validate 후 raw type 의 pass: 매 invariant 매 lose. 매 항상 새 type 의 return.
Parse 매 boundary X 의 매 매 layer 의 repeat: 매 perf 손실 + 매 동일 logic 의 duplicate.
Brand 의 매 runtime check 의 X: 매 cast 매 type-only — 매 parse function 매 항상 runtime check 포함.
Optional 의 abuse: 매 email?: string — 매 invariant 매 unclear. 매 명확한 Email | null.
Throw on parse fail (preference): 매 Result type / safeParse 의 매 prefer — 매 caller flow 매 explicit.