Skip to main content

sim_kernel/eval/
thunk.rs

1use std::sync::Mutex;
2
3use crate::{
4    env::Cx,
5    error::{Error, Result},
6    eval::Demand,
7    expr::Expr,
8    id::{CORE_THUNK_CLASS_ID, Symbol},
9    object::Object,
10    value::Value,
11};
12
13/// A delayed computation that produces a value when forced.
14///
15/// Thunks are how lazy and call-by-need eval policies defer argument
16/// evaluation: the value is computed only when [`force`](Thunk::force) is
17/// called to satisfy a [`Demand`]. The kernel defines the contract and two
18/// reference objects ([`LazyThunkObject`] and [`ThunkObject`]); libraries may
19/// supply their own.
20pub trait Thunk: Send + Sync {
21    /// Forces the delayed computation, returning its value.
22    fn force(&self, cx: &mut Cx, demand: Demand) -> Result<Value>;
23}
24
25enum ThunkState {
26    Pending { input: Expr, env: crate::env::Env },
27    Forcing,
28    Forced(Value),
29}
30
31struct LazyThunkState {
32    input: Expr,
33    env: crate::env::Env,
34    forcing: bool,
35}
36
37// sim-non-citizen(reason = "live delayed-evaluation handle; reconstruct the source expression and environment separately", kind = "handle", descriptor = "core/Expr")
38/// A non-memoizing thunk that re-evaluates its expression on every force.
39///
40/// Used by call-by-name style policies: forcing always re-runs the captured
41/// expression in its captured environment and never caches the result.
42pub struct LazyThunkObject {
43    state: Mutex<LazyThunkState>,
44}
45
46impl LazyThunkObject {
47    /// Creates a thunk capturing `input` and the environment `env`.
48    pub fn new(input: Expr, env: crate::env::Env) -> Self {
49        Self {
50            state: Mutex::new(LazyThunkState {
51                input,
52                env,
53                forcing: false,
54            }),
55        }
56    }
57}
58
59impl Object for LazyThunkObject {
60    fn display(&self, _cx: &mut Cx) -> Result<String> {
61        Ok("#<lazy-thunk>".to_owned())
62    }
63
64    fn as_any(&self) -> &dyn std::any::Any {
65        self
66    }
67}
68
69impl crate::ObjectCompat for LazyThunkObject {
70    fn class(&self, cx: &mut Cx) -> Result<Value> {
71        if let Some(value) = cx
72            .registry()
73            .class_by_symbol(&Symbol::qualified("core", "Thunk"))
74        {
75            return Ok(value.clone());
76        }
77        cx.factory()
78            .class_stub(CORE_THUNK_CLASS_ID, Symbol::qualified("core", "Thunk"))
79    }
80    fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
81        let state = self
82            .state
83            .lock()
84            .map_err(|_| Error::PoisonedLock("lazy thunk state"))?;
85        if state.forcing {
86            return Err(Error::RecursiveThunkForce);
87        }
88        Ok(state.input.clone())
89    }
90    fn as_thunk(&self) -> Option<&dyn Thunk> {
91        Some(self)
92    }
93}
94
95impl Thunk for LazyThunkObject {
96    fn force(&self, cx: &mut Cx, _demand: Demand) -> Result<Value> {
97        let (input, env) = {
98            let mut state = self
99                .state
100                .lock()
101                .map_err(|_| Error::PoisonedLock("lazy thunk state"))?;
102            if state.forcing {
103                return Err(Error::RecursiveThunkForce);
104            }
105            state.forcing = true;
106            (state.input.clone(), state.env.clone())
107        };
108        let evaluated = eval_input_with_env(cx, input, env);
109
110        let mut state = self
111            .state
112            .lock()
113            .map_err(|_| Error::PoisonedLock("lazy thunk state"))?;
114        state.forcing = false;
115        evaluated
116    }
117}
118
119// sim-non-citizen(reason = "live delayed-evaluation handle; reconstruct the source expression and environment separately", kind = "handle", descriptor = "core/Expr")
120/// A memoizing call-by-need thunk: evaluated at most once, then cached.
121///
122/// Used by [`NeedPolicy`](crate::NeedPolicy): the first successful force
123/// records the value for all later forces; a failed force leaves the thunk
124/// pending so it can be retried.
125pub struct ThunkObject {
126    state: Mutex<ThunkState>,
127}
128
129impl ThunkObject {
130    /// Creates a pending thunk capturing `input` and the environment `env`.
131    pub fn new(input: Expr, env: crate::env::Env) -> Self {
132        Self {
133            state: Mutex::new(ThunkState::Pending { input, env }),
134        }
135    }
136}
137
138impl Object for ThunkObject {
139    fn display(&self, _cx: &mut Cx) -> Result<String> {
140        Ok("#<thunk>".to_owned())
141    }
142
143    fn as_any(&self) -> &dyn std::any::Any {
144        self
145    }
146}
147
148impl crate::ObjectCompat for ThunkObject {
149    fn class(&self, cx: &mut Cx) -> Result<Value> {
150        if let Some(value) = cx
151            .registry()
152            .class_by_symbol(&Symbol::qualified("core", "Thunk"))
153        {
154            return Ok(value.clone());
155        }
156        cx.factory()
157            .class_stub(CORE_THUNK_CLASS_ID, Symbol::qualified("core", "Thunk"))
158    }
159    fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
160        let state = self
161            .state
162            .lock()
163            .map_err(|_| Error::PoisonedLock("thunk state"))?;
164        match &*state {
165            ThunkState::Pending { input, .. } => Ok(input.clone()),
166            ThunkState::Forcing => Err(Error::RecursiveThunkForce),
167            ThunkState::Forced(value) => value.object().as_expr(cx),
168        }
169    }
170    fn as_thunk(&self) -> Option<&dyn Thunk> {
171        Some(self)
172    }
173}
174
175impl Thunk for ThunkObject {
176    fn force(&self, cx: &mut Cx, _demand: Demand) -> Result<Value> {
177        let (input, env) = {
178            let mut state = self
179                .state
180                .lock()
181                .map_err(|_| Error::PoisonedLock("thunk state"))?;
182            match &*state {
183                ThunkState::Forced(value) => return Ok(value.clone()),
184                ThunkState::Forcing => return Err(Error::RecursiveThunkForce),
185                ThunkState::Pending { input, env } => {
186                    let pair = (input.clone(), env.clone());
187                    *state = ThunkState::Forcing;
188                    pair
189                }
190            }
191        };
192        let evaluated = eval_input_with_env(cx, input.clone(), env.clone());
193
194        let mut state = self
195            .state
196            .lock()
197            .map_err(|_| Error::PoisonedLock("thunk state"))?;
198        match evaluated {
199            Ok(value) => {
200                *state = ThunkState::Forced(value.clone());
201                Ok(value)
202            }
203            Err(err) => {
204                *state = ThunkState::Pending { input, env };
205                Err(err)
206            }
207        }
208    }
209}
210
211fn eval_input_with_env(cx: &mut Cx, input: Expr, env: crate::env::Env) -> Result<Value> {
212    cx.with_env(env, |cx| crate::eval::eval_expr_default(cx, input))
213}