Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

15장: 모나드, do 표기법, IO

새 파일

  • src/io_lower.rs

변경된 파일

  • src/desugar.rs
  • src/eval.rs
  • src/typecheck.rs
  • src/ast.rs
  • src/data.rs
  • src/elaborate.rs
  • src/fixity.rs
  • src/kinds.rs
  • src/layout.rs
  • src/lexer.rs
  • src/lib.rs
  • src/main.rs
  • src/primitives.rs
  • src/rename.rs
  • src/session.rs

동기

지금까지 우리 프로그램은 바깥 세계와 꽤 제한된 관계만 맺어 왔습니다. 드라이버는 main을 평가하고, 답을 출력한 뒤, 퇴근합니다. 질문을 던지고, 응답을 읽고, 다음에 무엇을 할지 결정할 수 있는 프로그램을 작성해 봅시다.

우리는 일반 표현식을 순수하게 유지하고 싶으므로, IO 값을 평가해도 그 효과는 수행되지 않습니다. 우리는 동작(action)을 값으로 표현하고, 명시적인 경계에서 그것을 실행할 것입니다. 이 구분 덕분에 우리가 이미 알고 있는 함수들을 사용해 동작을 만들고 조합할 수 있습니다.

14장의 타입 클래스는 이러한 조합을 기술하는 방법을 제공합니다. Functor, Applicative, Monad는 IO와 함께 동작하지만, Maybe, Either, 리스트에 대한 유용한 연산도 기술합니다. 우리는 먼저 이 연산들이 무엇을 하는지 살펴보고, 구문을 손으로 직접 전개해 보겠습니다. 용어에 신비로움을 덧씌우지 않아도 여기에는 이해할 것이 충분히 많습니다.

현재의 언어

이제 완전한 프로그램은 그 시그니처를 직접 작성했든 추론되었든 main :: IO ()를 가집니다. 가장 작은 예제는 새 Prelude 프린터를 호출합니다:

main = putStrLn "hello"

do 표기법은 일반 데이터에도 쓸 수 있습니다. 여기서는 두 번의 바인드가 Just에서 값을 꺼내고, pure가 같은 Maybe 인스턴스 안에서 결과를 만듭니다:

main = print (do { x <- Just 20; y <- Just 22; pure (x+y) })

블록은 반드시 식으로 끝나야 합니다. x <- action 문은 남은 문에서만 x를 바인딩합니다. let 문은 in 없이 지역 선언을 도입합니다. 패턴은 중첩될 수 있으며, 여기에는 튜플, 리스트, as-패턴, 명목적 레코드 패턴이 포함됩니다. 반박 가능한 패턴이 매칭에 실패하면 그 소스 위치와 함께 error를 호출합니다. Fun에는 MonadFail이 없으므로, 둘러싼 모나드가 Maybe일 때에도 실패는 런타임 오류가 됩니다.

putStr, putStrLn, getLine, print, exitWith는 프로그램의 외부 인터페이스를 제공합니다. mapM, mapM_, sequence_, when, unless는 흔히 쓰이는 순서화 패턴을 표현합니다. 진입점이 없거나 타입이 잘못되었으면 E0250을 보고합니다. 본문이 명시적 시그니처를 위반하면 그 대신 시그니처 검사 중에 E0203으로 실패할 수 있습니다.

이론

어떤 액션이 a를 만들어 내고, 프로그램의 나머지 부분은 그 a를 써서 다음 액션을 만드는 법을 안다고 가정해 봅시다. 바인드는 이 둘을 연결합니다. 두 번째 인자가 함수인 까닭은 다음 액션이 첫 번째 결과에 따라 달라질 수 있기 때문입니다. 이 타입을 왼쪽에서 오른쪽으로 천천히 읽어 보십시오 (Jones 1993).

IO의 경우, 소스 검사기는 IO를 추상적인 명목 생성자로 취급합니다. Core는 액션을 unit에서 그 결과로 가는 함수로 표현합니다:

unit 인자는 지연을 담아 둘 자리를 마련해 줍니다. 함수를 만든다고 해서 그 본문이 실행되지는 않습니다. returnIO는 저장해 둔 값을 반환하는 함수를 만들고, bindIO는 첫 번째 액션을 실행한 뒤 그 결과를 다음 액션에 넘기는 함수를 만듭니다. 부수 효과를 일으키는 기본 연산들은 그 본문 안에 들어 있습니다.

이 표현은 신뢰할 수 있는 Prelude와 컴파일러만 사용할 수 있습니다. 소스 프로그램에는 추상적인 IO a 값만 보이므로, 액션에 ()를 적용해 아무 곳에서나 실행할 수 없습니다. 바로 이 경계 덕분에 일반적인 Fun 식이 순수하게 유지됩니다.

엄격성은 여전히 적용됩니다. putStrLn "hello"를 만들면 그 문자열 인자를 평가하고 액션을 반환합니다. 출력은 하지 않습니다. error "bad"를 즉시 요구되는 인자로 넘겨 액션을 만들면, 액션이 실행되기 전에 실패할 수 있습니다. 순서화가 제어하는 것은 효과이지, 임의의 순수한 인자의 평가가 아닙니다. Haskell Report는 이와 유사한 do 변환을 기술하지만 (Simon Marlow 2010), 그 지연 평가를 Fun의 연산적 설명에 들여와서는 안 됩니다.

모나드 법칙은 인스턴스가 따라야 할 동작을 기술합니다. 컴파일러는 타입을 검사할 뿐, 그 등식들을 검사하지는 않습니다. 특히 비종료가 관련된 등식은 엄격한 언어에서 주의해서 다뤄야 합니다. 메커니즘은 여전히 14장의 사전 전달입니다. 평가기에 새로운 클래스 디스패치는 나타나지 않습니다.

구현

파서는 어휘적 이름 변경 전에 desugar::do_block을 호출합니다. 문장을 뒤에서부터 처리하면 남은 블록이 각 바인드의 컨티뉴에이션이 됩니다. 변수 패턴은 곧바로 람다가 됩니다. 반박 가능한 패턴은 실패 대안을 가진 case를 도입합니다. 이때 쓰는 임시 이름은 소스 식별자로 작성할 수 없습니다.

#![allow(unused)]
fn main() {
use crate::{
    data::{ConId, DataEnv},
    diagnostics::{Diagnostic, ErrorCode},
    renamed::{self, BindGroup, Expr, Kind, Pat, PatKind, Row, RowBody},
    session::{Session, Stop},
    source::Span,
    syn,
};
struct Desugar<'a> {
    sess: &'a mut Session,
    data: &'a DataEnv,
}
impl Desugar<'_> {
    fn node(&mut self, kind: Kind, span: Span) -> Expr {
        Expr {
            kind,
            span,
            node: self.sess.fresh_node(),
        }
    }
    fn constructor(&mut self, name: &str, span: Span) -> ConId {
        if let Some(info) = self.data.constructor(name) {
            return info.id.clone();
        }
        self.sess.emit(Diagnostic::error(
            ErrorCode::UnboundConstructor,
            format!("sugar requires constructor {name} from the Prelude"),
            span,
        ));
        ConId {
            index: 0,
            name: name.into(),
        }
    }
    fn tuple(&mut self, n: usize, span: Span) -> ConId {
        if !matches!(n, 0 | 2 | 3) {
            self.sess.emit(Diagnostic::error(
                ErrorCode::Parse,
                "only unit, pairs, and triples are supported",
                span,
            ));
        }
        self.constructor(
            &format!("({})", ",".repeat(n.saturating_sub(1))),
            span,
        )
    }
    fn pattern(&mut self, p: Pat) -> Pat {
        let span = p.span;
        let kind = match p.kind {
            PatKind::List(ps) => {
                let nil = self.constructor("[]", span);
                let cons = self.constructor(":", span);
                let mut tail = Pat {
                    kind: PatKind::Con(nil, vec![]),
                    span,
                };
                for p in ps.into_iter().rev() {
                    tail = Pat {
                        kind: PatKind::Con(
                            cons.clone(),
                            vec![self.pattern(p), tail],
                        ),
                        span,
                    };
                }
                return tail;
            }
            PatKind::Tuple(ps) => PatKind::Con(
                self.tuple(ps.len(), span),
                ps.into_iter().map(|p| self.pattern(p)).collect(),
            ),
            PatKind::Con(id, ps) => PatKind::Con(
                id,
                ps.into_iter().map(|p| self.pattern(p)).collect(),
            ),
            PatKind::As(id, p) => {
                PatKind::As(id, Box::new(self.pattern(*p)))
            }
            PatKind::Int(n) => PatKind::Int((n << 1) >> 1),
            other => other,
        };
        Pat { kind, span }
    }
    fn groups(&mut self, groups: Vec<BindGroup>) -> Vec<BindGroup> {
        groups
            .into_iter()
            .map(|mut g| {
                for b in &mut g.binds {
                    b.rhs = self.expression(b.rhs.clone());
                }
                g
            })
            .collect()
    }
    fn row(&mut self, row: Row) -> Row {
        let body = match row.body {
            RowBody::Plain(e) => {
                RowBody::Plain(Box::new(self.expression(*e)))
            }
            RowBody::Guarded(gs) => RowBody::Guarded(
                gs.into_iter()
                    .map(|(g, e)| (self.expression(g), self.expression(e)))
                    .collect(),
            ),
        };
        Row {
            patterns: row
                .patterns
                .into_iter()
                .map(|p| self.pattern(p))
                .collect(),
            body,
            locals: self.groups(row.locals),
            span: row.span,
        }
    }
    fn equations(&mut self, rows: Vec<Row>, span: Span) -> Expr {
        let mut rows: Vec<_> =
            rows.into_iter().map(|r| self.row(r)).collect();
        if rows.len() == 1
            && rows[0]
                .patterns
                .iter()
                .all(|p| matches!(p.kind, PatKind::Var(_)))
            && matches!(rows[0].body, RowBody::Plain(_))
        {
            let row = rows.pop().unwrap();
            let RowBody::Plain(body) = row.body else {
                unreachable!()
            };
            let mut body = *body;
            if !row.locals.is_empty() {
                body =
                    self.node(Kind::Let(row.locals, Box::new(body)), span);
            }
            for p in row.patterns.into_iter().rev() {
                let PatKind::Var(id) = p.kind else {
                    unreachable!()
                };
                body = self.node(Kind::Lam(id, Box::new(body)), span);
            }
            return body;
        }
        // ANCHOR: equations-match
        let params: Vec<_> = (0..rows[0].patterns.len())
            .map(|_| self.sess.fresh_id("$match"))
            .collect();
        let scruts = params
            .iter()
            .map(|id| self.node(Kind::Var(id.clone()), span))
            .collect();
        let mut body = self.node(Kind::Match(scruts, rows), span);
        for id in params.into_iter().rev() {
            body = self.node(Kind::Lam(id, Box::new(body)), span);
        }
        body
        // ANCHOR_END: equations-match
    }
    fn expression(&mut self, expr: Expr) -> Expr {
        let mut work = vec![(Some(expr), None)];
        let mut values: Vec<Expr> = Vec::new();
        while let Some((expr, saved)) = work.pop() {
            match expr {
                Some(Expr {
                    kind: Kind::App(f, a),
                    node,
                    span,
                }) => {
                    work.push((None, Some((node, span))));
                    work.push((Some(*a), None));
                    work.push((Some(*f), None));
                }
                Some(expr) => values.push(self.expression_leaf(expr)),
                None => {
                    let a = values.pop().expect("application argument");
                    let f = values.pop().expect("application function");
                    let (node, span) = saved.expect("application metadata");
                    values.push(Expr {
                        node,
                        span,
                        kind: Kind::App(Box::new(f), Box::new(a)),
                    });
                }
            }
        }
        values.pop().expect("desugared root")
    }
    fn expression_leaf(&mut self, expr: Expr) -> Expr {
        let span = expr.span;
        let kind = match expr.kind {
            Kind::Record(id, fields) => Kind::Record(
                id,
                fields
                    .into_iter()
                    .map(|(n, e)| (n, self.expression(e)))
                    .collect(),
            ),
            Kind::Project(e, n) => {
                Kind::Project(Box::new(self.expression(*e)), n)
            }
            Kind::Update(e, fields) => Kind::Update(
                Box::new(self.expression(*e)),
                fields
                    .into_iter()
                    .map(|(n, e)| (n, self.expression(e)))
                    .collect(),
            ),
            Kind::Annotated(e, t) => {
                Kind::Annotated(Box::new(self.expression(*e)), t)
            }
            Kind::Bool(b) => Kind::Con(
                self.constructor(if b { "True" } else { "False" }, span),
            ),
            Kind::Equations(rows) => return self.equations(rows, span),
            Kind::PatternLam(patterns, body) => {
                return self.equations(
                    vec![Row {
                        patterns,
                        body: RowBody::Plain(body),
                        locals: vec![],
                        span,
                    }],
                    span,
                );
            }
            Kind::Case(e, rows) => Kind::Match(
                vec![self.expression(*e)],
                rows.into_iter().map(|r| self.row(r)).collect(),
            ),
            Kind::If(c, a, b) => {
                let c = self.expression(*c);
                let a = self.expression(*a);
                let b = self.expression(*b);
                let rows = [("True", a), ("False", b)]
                    .into_iter()
                    .map(|(name, body)| Row {
                        patterns: vec![Pat {
                            kind: PatKind::Con(
                                self.constructor(name, span),
                                vec![],
                            ),
                            span,
                        }],
                        body: RowBody::Plain(Box::new(body)),
                        locals: vec![],
                        span,
                    })
                    .collect();
                Kind::Match(vec![c], rows)
            }
            Kind::List(es) => {
                let nil = self.constructor("[]", span);
                let cons = self.constructor(":", span);
                let mut tail = self.node(Kind::Con(nil), span);
                for e in es.into_iter().rev() {
                    let con = self.node(Kind::Con(cons.clone()), span);
                    let head = self.expression(e);
                    let app = self.node(
                        Kind::App(Box::new(con), Box::new(head)),
                        span,
                    );
                    tail = self.node(
                        Kind::App(Box::new(app), Box::new(tail)),
                        span,
                    );
                }
                return tail;
            }
            Kind::Tuple(es) => {
                let id = self.tuple(es.len(), span);
                let mut value = self.node(Kind::Con(id), span);
                for e in es {
                    let e = self.expression(e);
                    value = self.node(
                        Kind::App(Box::new(value), Box::new(e)),
                        span,
                    );
                }
                return value;
            }
            Kind::Lam(id, b) => {
                Kind::Lam(id, Box::new(self.expression(*b)))
            }
            Kind::App(a, b) => Kind::App(
                Box::new(self.expression(*a)),
                Box::new(self.expression(*b)),
            ),
            Kind::Let(gs, b) => {
                Kind::Let(self.groups(gs), Box::new(self.expression(*b)))
            }
            Kind::Neg(a) => Kind::Neg(Box::new(self.expression(*a))),
            Kind::Binary(op, a, b) => Kind::Binary(
                op,
                Box::new(self.expression(*a)),
                Box::new(self.expression(*b)),
            ),
            Kind::Match(..) => {
                unreachable!("Match is produced by this pass")
            }
            other => other,
        };
        Expr {
            kind,
            span,
            node: expr.node,
        }
    }
}
/// Produce Syn after names are resolved, preserving row scopes and guard order.
pub fn desugar(
    sess: &mut Session,
    program: renamed::Program,
) -> Result<syn::Program, Stop> {
    let groups = {
        let mut pass = Desugar {
            sess,
            data: &program.data,
        };
        pass.groups(program.groups)
    };
    sess.finish(syn::Program {
        classes: program.classes,
        data: program.data,
        groups,
        primitives: program.primitives,
        main: program.main,
        span: program.span,
    })
}
/// Statements are lowered before name resolution so ordinary lexical scopes apply.
pub enum Statement {
    Expr(crate::ast::Expr),
    Bind(crate::ast::Pat, crate::ast::Expr),
    Let(Vec<crate::ast::Binding>),
}
pub fn do_pattern(
    e: crate::ast::Expr,
) -> Result<crate::ast::Pat, Diagnostic> {
    use crate::ast::{Kind as K, Pat as P, PatKind as PK};
    let span = e.span;
    let kind = match e.kind {
        K::Chain(a, pairs, None) if pairs.is_empty() => {
            return do_pattern(*a);
        }
        K::Chain(a, pairs, None)
            if pairs.iter().all(|(op, _)| op == ":") =>
        {
            let mut elements = vec![do_pattern(*a)?];
            for (_, e) in pairs {
                elements.push(do_pattern(e)?);
            }
            let mut tail = elements.pop().unwrap();
            for head in elements.into_iter().rev() {
                tail = P {
                    kind: PK::Con(":".into(), vec![head, tail]),
                    span,
                };
            }
            return Ok(tail);
        }
        K::PatternAs(n, p) => PK::As(n, Box::new(do_pattern(*p)?)),
        K::Paren(a) => return do_pattern(*a),
        K::Var(n) if n == "_" => PK::Wild,
        K::Var(n) if n.starts_with(char::is_uppercase) => {
            PK::Con(n, vec![])
        }
        K::Var(n) => PK::Var(n),
        K::App(a, b) => {
            let mut p = do_pattern(*a)?;
            if let PK::Con(_, ps) = &mut p.kind {
                ps.push(do_pattern(*b)?);
                return Ok(p);
            } else {
                return Err(Diagnostic::error(
                    ErrorCode::Parse,
                    "invalid do pattern",
                    span,
                ));
            }
        }
        K::Int(n) => PK::Int(n),
        K::Double(n) => PK::Double(n),
        K::Char(c) => PK::Char(c),
        K::String(s) => PK::String(s),
        K::Neg(a) => {
            if let K::Int(n) = a.kind {
                PK::Int(-n)
            } else {
                return Err(Diagnostic::error(
                    ErrorCode::Parse,
                    "invalid negative pattern",
                    span,
                ));
            }
        }
        K::List(es) => PK::List(
            es.into_iter().map(do_pattern).collect::<Result<_, _>>()?,
        ),
        K::Tuple(es) => PK::Tuple(
            es.into_iter().map(do_pattern).collect::<Result<_, _>>()?,
        ),
        K::Update(a, fs) => {
            fn record(
                mut p: P,
                fields: Vec<(String, P)>,
            ) -> Result<P, Diagnostic> {
                p.kind = match p.kind {
                    PK::As(n, p) => {
                        PK::As(n, Box::new(record(*p, fields)?))
                    }
                    PK::Con(n, ps) if ps.is_empty() => {
                        PK::Record(n, fields)
                    }
                    _ => {
                        return Err(Diagnostic::error(
                            ErrorCode::Parse,
                            "invalid record pattern",
                            p.span,
                        ));
                    }
                };
                Ok(p)
            }
            let fields = fs
                .into_iter()
                .map(|(n, e)| Ok((n, do_pattern(e)?)))
                .collect::<Result<_, Diagnostic>>()?;
            return record(do_pattern(*a)?, fields);
        }
        K::Record(n, fs) => PK::Record(
            n,
            fs.into_iter()
                .map(|(n, e)| Ok((n, do_pattern(e)?)))
                .collect::<Result<_, Diagnostic>>()?,
        ),
        _ => {
            return Err(Diagnostic::error(
                ErrorCode::Parse,
                "invalid do pattern",
                span,
            ));
        }
    };
    Ok(P { kind, span })
}
/// Expand do syntax into ordinary applications, matches, and local bindings.
pub fn do_block(
    statements: Vec<Statement>,
    span: Span,
) -> Result<crate::ast::Expr, Diagnostic> {
    use crate::ast::{
        Expr as E, Kind as K, Pat as P, PatKind as PK, Row as R,
        RowBody as B,
    };
    let node = |kind| E { kind, span };
    let app = |a, b| node(K::App(Box::new(a), Box::new(b)));
    let mut statements = statements;
    let Some(Statement::Expr(mut body)) = statements.pop() else {
        return Err(Diagnostic::error(
            ErrorCode::Parse,
            "do must end with an expression",
            span,
        ));
    };
    for statement in statements.into_iter().rev() {
        body = match statement {
            Statement::Expr(e) => {
                app(app(node(K::Var(">>".into())), e), body)
            }
            Statement::Let(bs) => node(K::LetGroup(bs, Box::new(body))),
            Statement::Bind(p, e) => {
                let continuation = match &p.kind {
                    PK::Var(n) => node(K::Lam(n.clone(), Box::new(body))),
                    PK::Wild => node(K::Lam("$do".into(), Box::new(body))),
                    _ => {
                        let site = p.span;
                        let rows = vec![
                            R {
                                patterns: vec![p],
                                body: B::Plain(Box::new(body)),
                                locals: vec![],
                                span: site,
                            },
                            R {
                                patterns: vec![P {
                                    kind: PK::Wild,
                                    span: site,
                                }],
                                body: B::Plain(Box::new(E {
                                    kind: K::DoFail,
                                    span: site,
                                })),
                                locals: vec![],
                                span: site,
                            },
                        ];
                        node(K::Lam(
                            "$do".into(),
                            Box::new(node(K::Case(
                                Box::new(node(K::Var("$do".into()))),
                                rows,
                            ))),
                        ))
                    }
                };
                app(app(node(K::Var(">>=".into())), e), continuation)
            }
        };
    }
    Ok(body)
}
}

이 배치는 이름 변경 이후에 별도로 수행하는 디슈거링 패스와 다릅니다. 생성된 바인딩도 손으로 작성한 바인딩과 같은 스코프 검사기를 거칩니다. 보조 함수 do_pattern은 <- 왼쪽에 있는 패턴 모양의 구문을 검증합니다. 스팬을 지닌 DoFail 노드는 나중에 파일, 줄, 열 정보를 담은 일반적인 오류 호출로 변환됩니다. 어떤 do 노드도 Core에 도달하지 않습니다.

검사기가 IO 구현을 unit을 받는 함수와 연관 짓게 해 주는 것은 Prelude의 신뢰된 FileId뿐입니다. 사용자에게 보이는 타입 스킴은 명목적으로 유지됩니다. 정교화(elaboration) 후 io_lower::lower는 린트 전에 모든 Core 타입을 순회합니다. 바인더 타입, 타입 적용, 생성자 필드, match 결과 타입, 사전 타입이 그 대상입니다. 가장 바깥 수준의 IO a만 대체한다면 Monad IO 사전 같은 고계 종류 사용을 놓치게 됩니다. 이 순회는 적용되지 않은 생성자 IO도 부분 적용된 함수 생성자 (->) ()로 대체합니다.

실행기는 먼저 함수 클로저를 설치하고 엄격한 최상위 값을 의존성 순서대로 평가한 다음, 진입점을 unit에 적용합니다:

#![allow(unused)]
fn main() {
use crate::{
    ast::Op,
    core::{self, Bind, BindGroup, Expr, Id, Lit},
};
use std::{cell::RefCell, rc::Rc};
/// Language termination is separate from an ordinary runtime failure.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Failure {
    Runtime(String),
    Exit(i32),
}
impl From<String> for Failure {
    fn from(s: String) -> Self {
        Self::Runtime(s)
    }
}
impl From<&str> for Failure {
    fn from(s: &str) -> Self {
        Self::Runtime(s.into())
    }
}
impl std::fmt::Display for Failure {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Runtime(s) => write!(f, "{s}"),
            Self::Exit(n) => write!(f, "exit {n}"),
        }
    }
}
type Env = Vec<(Id, Value)>;
#[derive(Clone, Debug)]
/// A closure captures outer declarations and an optional immutable recursive-group recipe.
pub enum Value {
    Lit(Lit),
    Con(crate::data::ConId, Vec<Value>, Option<Vec<String>>),
    Closure {
        parameter: Id,
        body: Rc<Expr>,
        env: Rc<Env>,
        recursive: Option<Rc<Vec<Bind>>>,
    },
}
impl std::fmt::Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.print(f, 0)
    }
}
impl Value {
    fn print(
        &self,
        f: &mut std::fmt::Formatter<'_>,
        precedence: u8,
    ) -> std::fmt::Result {
        match self {
            Self::Lit(Lit::Double(n))
                if f64::from_bits(*n).is_sign_negative()
                    && precedence > 6 =>
            {
                write!(
                    f,
                    "({})",
                    crate::primitives::show_double(f64::from_bits(*n))
                )
            }
            Self::Lit(Lit::Int(n)) if *n < 0 && precedence > 6 => {
                write!(f, "({n})")
            }
            Self::Lit(l) => write!(f, "{l}"),
            Self::Closure { .. } => write!(f, "<function>"),
            Self::Con(id, fields, Some(labels)) => {
                if precedence > 10 {
                    write!(f, "(")?;
                }
                write!(f, "{} {{", id.name)?;
                for (i, (name, value)) in
                    labels.iter().zip(fields).enumerate()
                {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{name} = ")?;
                    value.print(f, 0)?;
                }
                write!(f, "}}")?;
                if precedence > 10 {
                    write!(f, ")")?;
                }
                Ok(())
            }
            Self::Con(id, fields, None) => match id.name.as_str() {
                "[]" => write!(f, "[]"),
                ":" => {
                    write!(f, "[")?;
                    let mut value = self;
                    let mut first = true;
                    while let Self::Con(c, fs, _) = value {
                        if c.name == "[]" {
                            break;
                        }
                        if !first {
                            write!(f, ",")?;
                        }
                        first = false;
                        fs[0].print(f, 0)?;
                        value = &fs[1];
                    }
                    write!(f, "]")
                }
                "()" | "(,)" | "(,,)" => {
                    write!(f, "(")?;
                    for (i, v) in fields.iter().enumerate() {
                        if i > 0 {
                            write!(f, ",")?;
                        }
                        v.print(f, 0)?;
                    }
                    write!(f, ")")
                }
                _ => {
                    let parens = precedence > 10 && !fields.is_empty();
                    if parens {
                        write!(f, "(")?;
                    }
                    write!(f, "{}", id.name)?;
                    for v in fields {
                        write!(f, " ")?;
                        v.print(f, 11)?;
                    }
                    if parens {
                        write!(f, ")")?;
                    }
                    Ok(())
                }
            },
        }
    }
}
pub fn normalize(n: i64) -> i64 {
    (n << 1) >> 1
}
/// Rebuild a recursive group's closures from a shared immutable recipe. The
/// recipe captures the outer environment, never the closures themselves.
// ANCHOR: recursive-closures
fn recursive_values(bindings: Rc<Vec<Bind>>, env: Rc<Env>) -> Env {
    let mut values = Vec::new();
    for binding in bindings.iter() {
        let mut rhs = &*binding.rhs;
        while let Expr::TyLam(_, inner) = rhs {
            rhs = inner;
        }
        let Expr::Lam(parameter, body) = rhs else {
            unreachable!("recursive lambda invariant")
        };
        let value = Value::Closure {
            parameter: parameter.clone(),
            body: Rc::new(*body.clone()),
            env: env.clone(),
            recursive: Some(bindings.clone()),
        };
        values.push((binding.name.clone(), value));
    }
    values
}
fn bind_group(
    group: &BindGroup,
    env: &mut Env,
    fuel: &mut usize,
    program: &core::Program,
    globals: &RefCell<Env>,
) -> Result<(), Failure> {
    if group.recursive {
        env.extend(recursive_values(
            Rc::new(group.binds.clone()),
            Rc::new(env.clone()),
        ));
    } else {
        for binding in &group.binds {
            let value =
                evaluate(&binding.rhs, env, fuel, program, globals)?;
            env.push((binding.name.clone(), value));
        }
    }
    Ok(())
}
fn evaluate(
    expr: &Expr,
    env: &Env,
    fuel: &mut usize,
    program: &core::Program,
    globals: &RefCell<Env>,
) -> Result<Value, Failure> {
    *fuel = fuel.checked_sub(1).ok_or("step limit exceeded")?;
    match expr {
        Expr::Lit(l) => Ok(Value::Lit(l.clone())),
        Expr::TyLam(_, body) | Expr::TyApp(body, _) => {
            evaluate(body, env, fuel, program, globals)
        }
        Expr::Global(n) => globals
            .borrow()
            .iter()
            .find(|(id, _)| id.unique() == n.unique())
            .map(|(_, v)| v.clone())
            .ok_or_else(|| {
                Failure::Runtime(format!(
                    "internal error: uninitialized global {n}"
                ))
            }),
        Expr::Var(n) => env
            .iter()
            .rev()
            .find(|(name, _)| name == n)
            .map(|(_, v)| v.clone())
            .ok_or_else(|| {
                Failure::Runtime(format!("internal error: unbound {n}"))
            }),
        Expr::Lam(parameter, body) => Ok(Value::Closure {
            parameter: parameter.clone(),
            body: Rc::new(*body.clone()),
            env: Rc::new(env.clone()),
            recursive: None,
        }),
        // ANCHOR: application
        Expr::App(function, argument) => {
            let function = evaluate(function, env, fuel, program, globals)?;
            let argument = evaluate(argument, env, fuel, program, globals)?;
            if let Value::Closure {
                parameter,
                body,
                env,
                recursive,
            } = &function
            {
                let mut local = (**env).clone();
                if let Some(bindings) = recursive {
                    local.extend(recursive_values(
                        bindings.clone(),
                        env.clone(),
                    ));
                }
                local.push((parameter.clone(), argument));
                evaluate(body, &local, fuel, program, globals)
            } else {
                Err("internal error: non-function application".into())
            }
        }
        // ANCHOR_END: application
        Expr::Let(group, body) => {
            let mut local = env.clone();
            bind_group(group, &mut local, fuel, program, globals)?;
            evaluate(body, &local, fuel, program, globals)
        }
        Expr::Con(id, _, fields) => Ok(Value::Con(
            id.clone(),
            fields
                .iter()
                .map(|e| evaluate(e, env, fuel, program, globals))
                .collect::<Result<_, _>>()?,
            program.constructors[id.index as usize].labels.clone(),
        )),
        Expr::PatternFail(span, _) => Err(failure(program, *span).into()),
        Expr::Match(m) => {
            let values = m
                .scruts
                .iter()
                .map(|id| {
                    evaluate(
                        &Expr::Var(id.clone()),
                        env,
                        fuel,
                        program,
                        globals,
                    )
                })
                .collect::<Result<Vec<_>, _>>()?;
            for row in &m.rows {
                let mut local = env.clone();
                if !row
                    .pats
                    .iter()
                    .zip(&values)
                    .all(|(p, v)| matches_pattern(p, v, &mut local))
                {
                    continue;
                }
                for group in &row.locals {
                    bind_group(group, &mut local, fuel, program, globals)?;
                }
                match &row.body {
                    core::RowBody::Plain(e) => {
                        return evaluate(e, &local, fuel, program, globals);
                    }
                    core::RowBody::Guarded(gs) => {
                        for (g, e) in gs {
                            let value = evaluate(
                                g, &local, fuel, program, globals,
                            )?;
                            if matches!(value,Value::Con(id,_,_) if id.name=="True")
                            {
                                return evaluate(
                                    e, &local, fuel, program, globals,
                                );
                            }
                        }
                    }
                }
            }
            Err(failure(program, m.span).into())
        }
        Expr::Prim(op, _, args, _) => {
            let values = args
                .iter()
                .map(|a| evaluate(a, env, fuel, program, globals))
                .collect::<Result<Vec<_>, _>>()?;
            primitive(*op, &values, program, globals)
        }
    }
}
fn primitive(
    op: Op,
    args: &[Value],
    program: &core::Program,
    _globals: &RefCell<Env>,
) -> Result<Value, Failure> {
    if let Op::Runtime(p) = op {
        return crate::primitives::evaluate(p, args, program);
    }
    if let [Value::Lit(Lit::String(a)), Value::Lit(Lit::String(b))] = args
        && op == Op::Append
    {
        return Ok(Value::Lit(Lit::String(format!("{a}{b}"))));
    }
    let [Value::Lit(Lit::Int(a)), Value::Lit(Lit::Int(b))] = args else {
        return Err("internal error: primitive operands".into());
    };
    if matches!(op, Op::Eq | Op::Lt | Op::Le) {
        let yes = match op {
            Op::Eq => a == b,
            Op::Lt => a < b,
            _ => a <= b,
        };
        let name = if yes { "True" } else { "False" };
        let c = program
            .constructors
            .iter()
            .find(|c| c.id.name == name)
            .ok_or("internal error: missing Bool constructor")?;
        return Ok(Value::Con(c.id.clone(), vec![], c.labels.clone()));
    }
    let value = match op {
        Op::Runtime(p) => {
            return crate::primitives::evaluate(p, args, program);
        }
        Op::Add => Lit::Int(normalize(a.wrapping_add(*b))),
        Op::Sub => Lit::Int(normalize(a.wrapping_sub(*b))),
        Op::Mul => Lit::Int(normalize(a.wrapping_mul(*b))),
        Op::Eq | Op::Lt | Op::Le => unreachable!(),
        Op::Div | Op::Mod => {
            if *b == 0 {
                return Err("divide by zero".into());
            }
            let (mut q, mut r) = (a / b, a % b);
            if r != 0 && (r < 0) != (*b < 0) {
                q -= 1;
                r += b;
            }
            Lit::Int(normalize(if op == Op::Div { q } else { r }))
        }
        Op::Append => {
            return Err("internal error: string append on Int".into());
        }
    };
    Ok(Value::Lit(value))
}
/// Evaluate top-level values exactly once in dependency order, then select main's value.
fn execute(
    program: &core::Program,
    mut fuel: usize,
    io: bool,
) -> Result<String, Failure> {
    let globals = RefCell::new(vec![]);
    for group in &program.groups {
        for b in &group.binds {
            if core::is_function(&b.rhs) {
                let value = evaluate(
                    &b.rhs,
                    &vec![],
                    &mut fuel,
                    program,
                    &globals,
                )?;
                globals.borrow_mut().push((b.name.clone(), value));
            }
        }
    }
    for group in &program.groups {
        for b in &group.binds {
            if !core::is_function(&b.rhs) {
                let value = evaluate(
                    &b.rhs,
                    &vec![],
                    &mut fuel,
                    program,
                    &globals,
                )?;
                globals.borrow_mut().push((b.name.clone(), value));
            }
        }
    }
    let value = globals
        .borrow()
        .iter()
        .find(|(n, _)| n.unique() == program.main.unique())
        .map(|(_, v)| v.clone())
        .ok_or("internal error: missing main")?;
    if io {
        let unit = program
            .constructors
            .iter()
            .find(|c| c.id.name == "()")
            .unwrap();
        let action = Expr::App(
            Box::new(Expr::Global(program.main.clone())),
            Box::new(Expr::Con(unit.id.clone(), vec![], vec![])),
        );
        evaluate(&action, &vec![], &mut fuel, program, &globals)?;
        Ok(String::new())
    } else {
        Ok(value.to_string())
    }
}
/// Evaluate a pure fragment; retained for compiler tests and REPL inspection.
pub fn run(program: &core::Program, fuel: usize) -> Result<String, String> {
    execute(program, fuel, false).map_err(|e| e.to_string())
}
fn failure(program: &core::Program, span: crate::source::Span) -> String {
    let site = program
        .locations
        .iter()
        .find(|(s, _)| *s == span)
        .map(|(_, s)| s.as_str())
        .unwrap_or("<unknown>:1:1");
    format!("pattern match failure at {site}")
}
fn matches_pattern(p: &core::Pat, v: &Value, env: &mut Env) -> bool {
    match p {
        core::Pat::Wild => true,
        core::Pat::Var(id) => {
            env.push((id.clone(), v.clone()));
            true
        }
        core::Pat::As(id, p) => {
            env.push((id.clone(), v.clone()));
            matches_pattern(p, v, env)
        }
        core::Pat::Lit(Lit::Double(n)) => {
            matches!(v,Value::Lit(Lit::Double(m)) if f64::from_bits(*n)==f64::from_bits(*m))
        }
        core::Pat::Lit(l) => matches!(v,Value::Lit(actual) if l==actual),
        core::Pat::Con(id, ps) => match v {
            Value::Con(actual, vs, _)
                if id == actual && ps.len() == vs.len() =>
            {
                ps.iter().zip(vs).all(|(p, v)| matches_pattern(p, v, env))
            }
            _ => false,
        },
    }
}
/// Initialize globals, then run the checked IO action exactly once.
pub fn run_io(program: &core::Program, fuel: usize) -> Result<(), Failure> {
    execute(program, fuel, true).map(|_| ())
}
}

사용되지 않는 최상위 액션도 여전히 시작 시점에 초기화되는 값입니다. 다음 회귀 테스트가 보여 주듯이, 그 본문에는 진입하지 않습니다:

unused = putStrLn "bad"

main = putStrLn "ok"

평가기는 언어 오류와 요청된 프로세스 종료를 구별합니다. 이 구별 덕분에 exitWith 7이 오류 메시지로 출력되지 않습니다:

#![allow(unused)]
fn main() {
use crate::{
    ast::Op,
    core::{self, Bind, BindGroup, Expr, Id, Lit},
};
use std::{cell::RefCell, rc::Rc};
impl From<String> for Failure {
    fn from(s: String) -> Self {
        Self::Runtime(s)
    }
}
impl From<&str> for Failure {
    fn from(s: &str) -> Self {
        Self::Runtime(s.into())
    }
}
impl std::fmt::Display for Failure {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Runtime(s) => write!(f, "{s}"),
            Self::Exit(n) => write!(f, "exit {n}"),
        }
    }
}
type Env = Vec<(Id, Value)>;
#[derive(Clone, Debug)]
/// A closure captures outer declarations and an optional immutable recursive-group recipe.
pub enum Value {
    Lit(Lit),
    Con(crate::data::ConId, Vec<Value>, Option<Vec<String>>),
    Closure {
        parameter: Id,
        body: Rc<Expr>,
        env: Rc<Env>,
        recursive: Option<Rc<Vec<Bind>>>,
    },
}
impl std::fmt::Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.print(f, 0)
    }
}
impl Value {
    fn print(
        &self,
        f: &mut std::fmt::Formatter<'_>,
        precedence: u8,
    ) -> std::fmt::Result {
        match self {
            Self::Lit(Lit::Double(n))
                if f64::from_bits(*n).is_sign_negative()
                    && precedence > 6 =>
            {
                write!(
                    f,
                    "({})",
                    crate::primitives::show_double(f64::from_bits(*n))
                )
            }
            Self::Lit(Lit::Int(n)) if *n < 0 && precedence > 6 => {
                write!(f, "({n})")
            }
            Self::Lit(l) => write!(f, "{l}"),
            Self::Closure { .. } => write!(f, "<function>"),
            Self::Con(id, fields, Some(labels)) => {
                if precedence > 10 {
                    write!(f, "(")?;
                }
                write!(f, "{} {{", id.name)?;
                for (i, (name, value)) in
                    labels.iter().zip(fields).enumerate()
                {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{name} = ")?;
                    value.print(f, 0)?;
                }
                write!(f, "}}")?;
                if precedence > 10 {
                    write!(f, ")")?;
                }
                Ok(())
            }
            Self::Con(id, fields, None) => match id.name.as_str() {
                "[]" => write!(f, "[]"),
                ":" => {
                    write!(f, "[")?;
                    let mut value = self;
                    let mut first = true;
                    while let Self::Con(c, fs, _) = value {
                        if c.name == "[]" {
                            break;
                        }
                        if !first {
                            write!(f, ",")?;
                        }
                        first = false;
                        fs[0].print(f, 0)?;
                        value = &fs[1];
                    }
                    write!(f, "]")
                }
                "()" | "(,)" | "(,,)" => {
                    write!(f, "(")?;
                    for (i, v) in fields.iter().enumerate() {
                        if i > 0 {
                            write!(f, ",")?;
                        }
                        v.print(f, 0)?;
                    }
                    write!(f, ")")
                }
                _ => {
                    let parens = precedence > 10 && !fields.is_empty();
                    if parens {
                        write!(f, "(")?;
                    }
                    write!(f, "{}", id.name)?;
                    for v in fields {
                        write!(f, " ")?;
                        v.print(f, 11)?;
                    }
                    if parens {
                        write!(f, ")")?;
                    }
                    Ok(())
                }
            },
        }
    }
}
pub fn normalize(n: i64) -> i64 {
    (n << 1) >> 1
}
/// Rebuild a recursive group's closures from a shared immutable recipe. The
/// recipe captures the outer environment, never the closures themselves.
// ANCHOR: recursive-closures
fn recursive_values(bindings: Rc<Vec<Bind>>, env: Rc<Env>) -> Env {
    let mut values = Vec::new();
    for binding in bindings.iter() {
        let mut rhs = &*binding.rhs;
        while let Expr::TyLam(_, inner) = rhs {
            rhs = inner;
        }
        let Expr::Lam(parameter, body) = rhs else {
            unreachable!("recursive lambda invariant")
        };
        let value = Value::Closure {
            parameter: parameter.clone(),
            body: Rc::new(*body.clone()),
            env: env.clone(),
            recursive: Some(bindings.clone()),
        };
        values.push((binding.name.clone(), value));
    }
    values
}
fn bind_group(
    group: &BindGroup,
    env: &mut Env,
    fuel: &mut usize,
    program: &core::Program,
    globals: &RefCell<Env>,
) -> Result<(), Failure> {
    if group.recursive {
        env.extend(recursive_values(
            Rc::new(group.binds.clone()),
            Rc::new(env.clone()),
        ));
    } else {
        for binding in &group.binds {
            let value =
                evaluate(&binding.rhs, env, fuel, program, globals)?;
            env.push((binding.name.clone(), value));
        }
    }
    Ok(())
}
fn evaluate(
    expr: &Expr,
    env: &Env,
    fuel: &mut usize,
    program: &core::Program,
    globals: &RefCell<Env>,
) -> Result<Value, Failure> {
    *fuel = fuel.checked_sub(1).ok_or("step limit exceeded")?;
    match expr {
        Expr::Lit(l) => Ok(Value::Lit(l.clone())),
        Expr::TyLam(_, body) | Expr::TyApp(body, _) => {
            evaluate(body, env, fuel, program, globals)
        }
        Expr::Global(n) => globals
            .borrow()
            .iter()
            .find(|(id, _)| id.unique() == n.unique())
            .map(|(_, v)| v.clone())
            .ok_or_else(|| {
                Failure::Runtime(format!(
                    "internal error: uninitialized global {n}"
                ))
            }),
        Expr::Var(n) => env
            .iter()
            .rev()
            .find(|(name, _)| name == n)
            .map(|(_, v)| v.clone())
            .ok_or_else(|| {
                Failure::Runtime(format!("internal error: unbound {n}"))
            }),
        Expr::Lam(parameter, body) => Ok(Value::Closure {
            parameter: parameter.clone(),
            body: Rc::new(*body.clone()),
            env: Rc::new(env.clone()),
            recursive: None,
        }),
        // ANCHOR: application
        Expr::App(function, argument) => {
            let function = evaluate(function, env, fuel, program, globals)?;
            let argument = evaluate(argument, env, fuel, program, globals)?;
            if let Value::Closure {
                parameter,
                body,
                env,
                recursive,
            } = &function
            {
                let mut local = (**env).clone();
                if let Some(bindings) = recursive {
                    local.extend(recursive_values(
                        bindings.clone(),
                        env.clone(),
                    ));
                }
                local.push((parameter.clone(), argument));
                evaluate(body, &local, fuel, program, globals)
            } else {
                Err("internal error: non-function application".into())
            }
        }
        // ANCHOR_END: application
        Expr::Let(group, body) => {
            let mut local = env.clone();
            bind_group(group, &mut local, fuel, program, globals)?;
            evaluate(body, &local, fuel, program, globals)
        }
        Expr::Con(id, _, fields) => Ok(Value::Con(
            id.clone(),
            fields
                .iter()
                .map(|e| evaluate(e, env, fuel, program, globals))
                .collect::<Result<_, _>>()?,
            program.constructors[id.index as usize].labels.clone(),
        )),
        Expr::PatternFail(span, _) => Err(failure(program, *span).into()),
        Expr::Match(m) => {
            let values = m
                .scruts
                .iter()
                .map(|id| {
                    evaluate(
                        &Expr::Var(id.clone()),
                        env,
                        fuel,
                        program,
                        globals,
                    )
                })
                .collect::<Result<Vec<_>, _>>()?;
            for row in &m.rows {
                let mut local = env.clone();
                if !row
                    .pats
                    .iter()
                    .zip(&values)
                    .all(|(p, v)| matches_pattern(p, v, &mut local))
                {
                    continue;
                }
                for group in &row.locals {
                    bind_group(group, &mut local, fuel, program, globals)?;
                }
                match &row.body {
                    core::RowBody::Plain(e) => {
                        return evaluate(e, &local, fuel, program, globals);
                    }
                    core::RowBody::Guarded(gs) => {
                        for (g, e) in gs {
                            let value = evaluate(
                                g, &local, fuel, program, globals,
                            )?;
                            if matches!(value,Value::Con(id,_,_) if id.name=="True")
                            {
                                return evaluate(
                                    e, &local, fuel, program, globals,
                                );
                            }
                        }
                    }
                }
            }
            Err(failure(program, m.span).into())
        }
        Expr::Prim(op, _, args, _) => {
            let values = args
                .iter()
                .map(|a| evaluate(a, env, fuel, program, globals))
                .collect::<Result<Vec<_>, _>>()?;
            primitive(*op, &values, program, globals)
        }
    }
}
fn primitive(
    op: Op,
    args: &[Value],
    program: &core::Program,
    _globals: &RefCell<Env>,
) -> Result<Value, Failure> {
    if let Op::Runtime(p) = op {
        return crate::primitives::evaluate(p, args, program);
    }
    if let [Value::Lit(Lit::String(a)), Value::Lit(Lit::String(b))] = args
        && op == Op::Append
    {
        return Ok(Value::Lit(Lit::String(format!("{a}{b}"))));
    }
    let [Value::Lit(Lit::Int(a)), Value::Lit(Lit::Int(b))] = args else {
        return Err("internal error: primitive operands".into());
    };
    if matches!(op, Op::Eq | Op::Lt | Op::Le) {
        let yes = match op {
            Op::Eq => a == b,
            Op::Lt => a < b,
            _ => a <= b,
        };
        let name = if yes { "True" } else { "False" };
        let c = program
            .constructors
            .iter()
            .find(|c| c.id.name == name)
            .ok_or("internal error: missing Bool constructor")?;
        return Ok(Value::Con(c.id.clone(), vec![], c.labels.clone()));
    }
    let value = match op {
        Op::Runtime(p) => {
            return crate::primitives::evaluate(p, args, program);
        }
        Op::Add => Lit::Int(normalize(a.wrapping_add(*b))),
        Op::Sub => Lit::Int(normalize(a.wrapping_sub(*b))),
        Op::Mul => Lit::Int(normalize(a.wrapping_mul(*b))),
        Op::Eq | Op::Lt | Op::Le => unreachable!(),
        Op::Div | Op::Mod => {
            if *b == 0 {
                return Err("divide by zero".into());
            }
            let (mut q, mut r) = (a / b, a % b);
            if r != 0 && (r < 0) != (*b < 0) {
                q -= 1;
                r += b;
            }
            Lit::Int(normalize(if op == Op::Div { q } else { r }))
        }
        Op::Append => {
            return Err("internal error: string append on Int".into());
        }
    };
    Ok(Value::Lit(value))
}
/// Evaluate top-level values exactly once in dependency order, then select main's value.
fn execute(
    program: &core::Program,
    mut fuel: usize,
    io: bool,
) -> Result<String, Failure> {
    let globals = RefCell::new(vec![]);
    for group in &program.groups {
        for b in &group.binds {
            if core::is_function(&b.rhs) {
                let value = evaluate(
                    &b.rhs,
                    &vec![],
                    &mut fuel,
                    program,
                    &globals,
                )?;
                globals.borrow_mut().push((b.name.clone(), value));
            }
        }
    }
    for group in &program.groups {
        for b in &group.binds {
            if !core::is_function(&b.rhs) {
                let value = evaluate(
                    &b.rhs,
                    &vec![],
                    &mut fuel,
                    program,
                    &globals,
                )?;
                globals.borrow_mut().push((b.name.clone(), value));
            }
        }
    }
    let value = globals
        .borrow()
        .iter()
        .find(|(n, _)| n.unique() == program.main.unique())
        .map(|(_, v)| v.clone())
        .ok_or("internal error: missing main")?;
    if io {
        let unit = program
            .constructors
            .iter()
            .find(|c| c.id.name == "()")
            .unwrap();
        let action = Expr::App(
            Box::new(Expr::Global(program.main.clone())),
            Box::new(Expr::Con(unit.id.clone(), vec![], vec![])),
        );
        evaluate(&action, &vec![], &mut fuel, program, &globals)?;
        Ok(String::new())
    } else {
        Ok(value.to_string())
    }
}
/// Evaluate a pure fragment; retained for compiler tests and REPL inspection.
pub fn run(program: &core::Program, fuel: usize) -> Result<String, String> {
    execute(program, fuel, false).map_err(|e| e.to_string())
}
/// Initialize globals, then run the checked IO action exactly once.
pub fn run_io(program: &core::Program, fuel: usize) -> Result<(), Failure> {
    execute(program, fuel, true).map(|_| ())
}
fn failure(program: &core::Program, span: crate::source::Span) -> String {
    let site = program
        .locations
        .iter()
        .find(|(s, _)| *s == span)
        .map(|(_, s)| s.as_str())
        .unwrap_or("<unknown>:1:1");
    format!("pattern match failure at {site}")
}
fn matches_pattern(p: &core::Pat, v: &Value, env: &mut Env) -> bool {
    match p {
        core::Pat::Wild => true,
        core::Pat::Var(id) => {
            env.push((id.clone(), v.clone()));
            true
        }
        core::Pat::As(id, p) => {
            env.push((id.clone(), v.clone()));
            matches_pattern(p, v, env)
        }
        core::Pat::Lit(Lit::Double(n)) => {
            matches!(v,Value::Lit(Lit::Double(m)) if f64::from_bits(*n)==f64::from_bits(*m))
        }
        core::Pat::Lit(l) => matches!(v,Value::Lit(actual) if l==actual),
        core::Pat::Con(id, ps) => match v {
            Value::Con(actual, vs, _)
                if id == actual && ps.len() == vs.len() =>
            {
                ps.iter().zip(vs).all(|(p, v)| matches_pattern(p, v, env))
            }
            _ => false,
        },
    }
}
/// Language termination is separate from an ordinary runtime failure.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Failure {
    Runtime(String),
    Exit(i32),
}
}

드라이버는 실행이 끝날 때와 런타임 진단을 출력하기 전에 stdout을 플러시합니다. 입력 프리미티브는 읽기 전에 플러시하므로, 프로그램이 블록되기 전에 프롬프트가 사용자에게 전달됩니다. 테스트 실행기는 .in 파일을 받아 각 엔진에 동일한 입력을 보냅니다. 파이프 핸드셰이크 회귀 테스트는 터미널 버퍼링과 무관하게 플러시를 검사합니다.

여기서 기존 프로그램은 약간 손봐야 합니다. 드라이버가 더 이상 main을 대신 출력해 주지 않으므로, 출력 코드를 프로그램 안에 직접 넣습니다. 값에 Show 인스턴스가 없다면 인스턴스를 직접 제공하거나 그 값에서 출력할 수 있는 부분을 골라야 합니다. 기존 값 프린터는 컴파일러 조각을 검사할 수 있도록 --print-value를 통해 계속 사용할 수 있습니다.

실행해 보기

저장소 루트에서 이 장의 크레이트로 들어가십시오. 다음 명령은 명시적인 액션의 출력과 추론된 진입점 타입을 출력합니다:

$ cd crates/ch15_io
$ cargo run -q -- run tests/programs/io/hello.fun --lint
hello
$ cargo run -q -- dump --pass=types tests/programs/io/hello.fun
main : IO ()
$ cargo run -q -- run tests/programs/io/deferred.fun --lint
ok

--pass=core를 사용하면 사전 인자와 unit을 받는 람다를 찾을 수 있습니다. 사용되지 않는 출력 액션과 main이 실제로 순차 실행하는 출력 액션을 비교해 볼 만합니다. 둘 다 값이지만, 본문이 실행되는 것은 후자뿐입니다. 이 장에서도 우리의 실행 엔진은 여전히 Core 인터프리터입니다.

연습 문제

  1. 준비 운동. 예제의 두 번째 Just를 적절히 타입을 표기한 Nothing으로 바꾸십시오. 그 컨티뉴에이션이 호출되지 않는 이유를 설명하십시오.
  2. 준비 운동. 사용되지 않는 출력 액션을 만드는 경우와, 문자열 인자가 error를 호출하는 출력 액션을 만드는 경우를 비교하십시오.
  3. 중급. let, 튜플 바인드, 섀도잉 바인드를 포함하는 do 블록에 대해 생성되는 스코프를 추적하십시오.
  4. 중급. Core에서 Monad IO 사전을 살펴보고 IO 낮추기(lowering)의 영향을 받는 모든 타입을 찾아내십시오.
  5. 고급. 파이프 핸드셰이크 테스트를 확장하여 프롬프트를 두 번 내보내고, 터미널의 줄 버퍼링에 의존하지 않고 그 순서를 검증하십시오.

더 읽을거리


원문 © Stephen Diehl · Write You a Haskell · 산문 CC BY-SA 4.0 · 코드 MIT
yeokja 한국어 번역 · Anthropic 사의 claude-opus-5-5 모델을 활용하여 번역되었으며 학습을 모두 비허용한 상태로 작업하였습니다 · 이 문서는 원문을 수정한 비공식 번역본이며 산문은 같은 CC BY-SA 4.0으로 제공합니다.