Skip to main content

sim_lib_numbers_func/implementation/
value.rs

1//! The `Func` function value: variables plus a labelled CAS/native body, with
2//! its metadata and arithmetic over function values.
3
4use std::{any::Any, sync::Arc};
5
6use sim_kernel::{
7    Args, Callable, ClassRef, Cx, DefaultFactory, Error, Expr, Factory, NumberValue, Object,
8    ObjectEncode, Result, ShapeRef, Symbol, Value,
9};
10use sim_lib_numbers_cas::{CasExpr, cas_expr_to_surface_expr, free_vars, value_to_cas_expr};
11use sim_lib_numbers_cas_eval::eval_cas;
12use sim_shape::{AnyShape, ListShape, Shape, shape_value};
13
14use super::domain::{func_class_symbol, func_domain_symbol};
15use super::function::{child_env_with_args, vars_expr};
16
17mod ops;
18
19pub(crate) use ops::register_value_ops;
20
21/// A native (Rust-backed) function body: a closure invoked with the runtime
22/// context and the evaluated argument values, used when a `Func` has no CAS body.
23pub type NativeFn = Arc<dyn Fn(&mut Cx, &[Value]) -> Result<Value> + Send + Sync>;
24
25/// Out-of-band annotations attached to a [`Func`] value.
26#[derive(Clone, Default)]
27pub struct FuncMetadata {
28    /// Symbol identifying where this function came from (for example, an
29    /// elementary-function name), when known.
30    pub source: Option<Symbol>,
31    /// Optional hint naming the differentiator that should handle `grad`/`diff`
32    /// for this function.
33    pub differentiator_hint: Option<Symbol>,
34    /// Arbitrary caller-supplied value carried alongside the function.
35    pub payload: Option<Value>,
36}
37
38#[derive(Clone)]
39enum FuncBody {
40    Symbolic(CasExpr),
41    Native {
42        native: NativeFn,
43        symbolic_status: SymbolicStatus,
44    },
45    Dual {
46        cas: CasExpr,
47        native: NativeFn,
48    },
49}
50
51/// Whether a [`Func`] exposes an exact symbolic body, is differentiable through
52/// a named hint, or has lost its symbolic body for a named reason.
53#[derive(Clone, Debug, PartialEq, Eq)]
54pub enum SymbolicStatus {
55    /// A CAS body is available and can be inspected by symbolic tools.
56    Available,
57    /// The function is native-only, but metadata names a differentiator that
58    /// can handle it numerically or exactly outside the CAS path.
59    ProvidedByHint,
60    /// The function is native-only because symbolic information was lost.
61    Lost {
62        /// The machine-readable reason for the loss.
63        reason: Symbol,
64    },
65}
66
67impl SymbolicStatus {
68    /// Reason used for ordinary native functions with no symbolic body.
69    pub fn native_only() -> Self {
70        Self::Lost {
71            reason: Symbol::qualified("numbers/func", "native-only"),
72        }
73    }
74
75    /// Reason used when arithmetic combines a symbolic body with a native-only
76    /// body and must keep only the executable native result.
77    pub fn mixed_native() -> Self {
78        Self::Lost {
79            reason: Symbol::qualified("numbers/func", "mixed-native"),
80        }
81    }
82
83    fn status_symbol(&self) -> Symbol {
84        match self {
85            Self::Available => Symbol::qualified("numbers/func", "available"),
86            Self::ProvidedByHint => Symbol::qualified("numbers/func", "provided-by-hint"),
87            Self::Lost { .. } => Symbol::qualified("numbers/func", "lost"),
88        }
89    }
90
91    fn reason(&self) -> Option<&Symbol> {
92        match self {
93            Self::Lost { reason } => Some(reason),
94            _ => None,
95        }
96    }
97}
98
99/// A callable function value in the `Func` number domain: its bound variables
100/// plus one labelled symbolic, native, or internal dual body.
101#[derive(Clone)]
102pub struct Func {
103    /// The ordered parameter symbols bound when the function is invoked.
104    pub vars: Vec<Symbol>,
105    body: FuncBody,
106    /// Out-of-band metadata describing the function.
107    pub metadata: FuncMetadata,
108}
109
110impl Func {
111    fn new(vars: Vec<Symbol>, body: FuncBody, metadata: FuncMetadata) -> Self {
112        Self {
113            vars,
114            body,
115            metadata,
116        }
117    }
118
119    /// Builds a function with a symbolic (CAS) body and default metadata.
120    pub fn symbolic(vars: Vec<Symbol>, body_cas: CasExpr) -> Self {
121        Self::symbolic_with(vars, body_cas, FuncMetadata::default())
122    }
123
124    /// Builds a function with a symbolic (CAS) body and caller-supplied metadata.
125    pub fn symbolic_with(vars: Vec<Symbol>, body_cas: CasExpr, metadata: FuncMetadata) -> Self {
126        Self::new(vars, FuncBody::Symbolic(body_cas), metadata)
127    }
128
129    /// Builds a function with a native (Rust closure) body and default metadata.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use std::sync::Arc;
135    /// use sim_kernel::Symbol;
136    /// use sim_lib_numbers_func::Func;
137    ///
138    /// let func = Func::native(
139    ///     vec![Symbol::new("x")],
140    ///     Arc::new(|_cx, args| Ok(args[0].clone())),
141    /// );
142    /// assert_eq!(func.vars, vec![Symbol::new("x")]);
143    /// assert!(func.body_cas().is_none());
144    /// assert!(func.is_native());
145    /// ```
146    pub fn native(vars: Vec<Symbol>, body_native: NativeFn) -> Self {
147        Self::native_with(vars, body_native, FuncMetadata::default())
148    }
149
150    /// Builds a function with a native body and caller-supplied metadata.
151    pub fn native_with(vars: Vec<Symbol>, body_native: NativeFn, metadata: FuncMetadata) -> Self {
152        let status = if metadata.differentiator_hint.is_some() {
153            SymbolicStatus::ProvidedByHint
154        } else {
155            SymbolicStatus::native_only()
156        };
157        Self::native_with_status(vars, body_native, metadata, status)
158    }
159
160    fn native_with_status(
161        vars: Vec<Symbol>,
162        body_native: NativeFn,
163        metadata: FuncMetadata,
164        symbolic_status: SymbolicStatus,
165    ) -> Self {
166        Self::new(
167            vars,
168            FuncBody::Native {
169                native: body_native,
170                symbolic_status,
171            },
172            metadata,
173        )
174    }
175
176    /// Builds an internal dual-body function whose native body is derived from
177    /// the same operation as the symbolic body.
178    pub(crate) fn dual_with(
179        vars: Vec<Symbol>,
180        body_cas: CasExpr,
181        body_native: NativeFn,
182        metadata: FuncMetadata,
183    ) -> Self {
184        Self::new(
185            vars,
186            FuncBody::Dual {
187                cas: body_cas,
188                native: body_native,
189            },
190            metadata,
191        )
192    }
193
194    /// Returns the symbolic body advertised by this function, when available.
195    pub fn body_cas(&self) -> Option<&CasExpr> {
196        match &self.body {
197            FuncBody::Symbolic(body) | FuncBody::Dual { cas: body, .. } => Some(body),
198            FuncBody::Native { .. } => None,
199        }
200    }
201
202    fn body_native(&self) -> Option<&NativeFn> {
203        match &self.body {
204            FuncBody::Native { native: body, .. } | FuncBody::Dual { native: body, .. } => {
205                Some(body)
206            }
207            FuncBody::Symbolic(_) => None,
208        }
209    }
210
211    /// Returns whether this function carries a native body.
212    pub fn is_native(&self) -> bool {
213        self.body_native().is_some()
214    }
215
216    /// Returns the symbolic-body status for this function.
217    pub fn symbolic_status(&self) -> SymbolicStatus {
218        match &self.body {
219            FuncBody::Symbolic(_) | FuncBody::Dual { .. } => SymbolicStatus::Available,
220            FuncBody::Native {
221                symbolic_status, ..
222            } => symbolic_status.clone(),
223        }
224    }
225
226    fn native_extension_payload(&self) -> Expr {
227        let status = self.symbolic_status();
228        let mut fields = vec![(
229            Expr::Symbol(Symbol::new("symbolic-status")),
230            Expr::Symbol(status.status_symbol()),
231        )];
232        if let Some(reason) = status.reason() {
233            fields.push((
234                Expr::Symbol(Symbol::new("symbolic-loss-reason")),
235                Expr::Symbol(reason.clone()),
236            ));
237        }
238        if let Some(hint) = &self.metadata.differentiator_hint {
239            fields.push((
240                Expr::Symbol(Symbol::new("differentiator-hint")),
241                Expr::Symbol(hint.clone()),
242            ));
243        }
244        Expr::Map(fields)
245    }
246
247    fn invoke(&self, cx: &mut Cx, args: &[Value]) -> Result<Value> {
248        if args.len() != self.vars.len() {
249            return Err(Error::Eval(format!(
250                "function expected {} arguments but received {}",
251                self.vars.len(),
252                args.len()
253            )));
254        }
255        if let Some(body_native) = self.body_native() {
256            return body_native(cx, args);
257        }
258        let body_cas = self
259            .body_cas()
260            .expect("FuncBody always contains a symbolic or native body");
261        let env = child_env_with_args(cx.env(), &self.vars, args)?;
262        cx.with_env(env.clone(), |cx| eval_cas(cx, body_cas, &env))
263    }
264}
265
266impl Object for Func {
267    fn display(&self, cx: &mut Cx) -> Result<String> {
268        if let Some(body_cas) = self.body_cas() {
269            return Ok(format!(
270                "#<func {:?} -> {:?}>",
271                self.vars,
272                cas_expr_to_surface_expr(cx, body_cas)?
273            ));
274        }
275        Ok(format!("#<native-func {:?}>", self.vars))
276    }
277
278    fn as_any(&self) -> &dyn Any {
279        self
280    }
281}
282
283impl sim_kernel::ObjectCompat for Func {
284    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
285        if let Some(value) = cx.registry().class_by_symbol(&func_class_symbol()) {
286            return Ok(value.clone());
287        }
288        DefaultFactory.class_stub(
289            sim_kernel::CORE_NUMBER_CLASS_ID,
290            Symbol::qualified("core", "Number"),
291        )
292    }
293    fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
294        let Some(body_cas) = self.body_cas() else {
295            return Ok(Expr::Extension {
296                tag: func_class_symbol(),
297                payload: Box::new(self.native_extension_payload()),
298            });
299        };
300        Ok(Expr::Call {
301            operator: Box::new(Expr::Symbol(Symbol::new("fn"))),
302            args: vec![
303                vars_expr(&self.vars),
304                cas_expr_to_surface_expr(cx, body_cas)?,
305            ],
306        })
307    }
308    fn as_table(&self, cx: &mut Cx) -> Result<Value> {
309        let vars = cx.factory().list(
310            self.vars
311                .iter()
312                .cloned()
313                .map(|var| cx.factory().symbol(var))
314                .collect::<Result<Vec<_>>>()?,
315        )?;
316        let body_expr = self
317            .body_cas()
318            .map(|body| cas_expr_to_surface_expr(cx, body))
319            .transpose()?;
320        let body = match self.body_cas() {
321            Some(_) => cx
322                .factory()
323                .expr(body_expr.expect("body expr should exist when body_cas is present"))?,
324            None => cx.factory().nil()?,
325        };
326        let native = cx.factory().bool(self.is_native())?;
327        let symbolic_status = self.symbolic_status();
328        let mut fields = vec![
329            (Symbol::new("kind"), cx.factory().string("func".to_owned())?),
330            (Symbol::new("vars"), vars),
331            (Symbol::new("body"), body),
332            (Symbol::new("native"), native),
333            (
334                Symbol::new("symbolic-status"),
335                cx.factory().symbol(symbolic_status.status_symbol())?,
336            ),
337        ];
338        if let Some(reason) = symbolic_status.reason() {
339            fields.push((
340                Symbol::new("symbolic-loss-reason"),
341                cx.factory().symbol(reason.clone())?,
342            ));
343        }
344        cx.factory().table(fields)
345    }
346    fn as_callable(&self) -> Option<&dyn Callable> {
347        Some(self)
348    }
349    fn as_number_value(&self) -> Option<&dyn NumberValue> {
350        Some(self)
351    }
352    fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
353        Some(self)
354    }
355}
356
357impl Callable for Func {
358    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
359        self.invoke(cx, args.values())
360    }
361
362    fn browse_args_shape(&self, _cx: &mut Cx) -> Result<Option<ShapeRef>> {
363        let items = self
364            .vars
365            .iter()
366            .map(|_| Arc::new(AnyShape) as Arc<dyn Shape>)
367            .collect();
368        Ok(Some(shape_value(
369            Symbol::qualified(func_class_symbol().to_string(), "args"),
370            Arc::new(ListShape::new(items)),
371        )))
372    }
373}
374
375impl NumberValue for Func {
376    fn number_domain(&self, _cx: &mut Cx) -> Result<Symbol> {
377        Ok(func_domain_symbol())
378    }
379}
380
381impl sim_citizen::Citizen for Func {
382    fn citizen_symbol() -> Symbol {
383        func_class_symbol()
384    }
385
386    fn citizen_version() -> u32 {
387        0
388    }
389
390    fn citizen_arity() -> usize {
391        2
392    }
393
394    fn citizen_fields() -> &'static [&'static str] {
395        &["vars", "body"]
396    }
397}
398
399/// Wraps a [`Func`] into a runtime [`Value`] in the `Func` number domain.
400pub fn build_func_value(cx: &mut Cx, func: Func) -> Result<Value> {
401    cx.factory().opaque(Arc::new(func))
402}
403
404pub(crate) fn build_constant_func_value(cx: &mut Cx, value: Value) -> Result<Value> {
405    let body = value_to_cas_expr(cx, value)?;
406    let vars = free_vars(&body);
407    build_func_value(cx, Func::symbolic(vars, body))
408}