adam_rs/relationship.rs
1//! Relationships and methods in the property model bipartite graph.
2//!
3//! Each relationship holds a list of [`Method`]s; the planner selects one
4//! method per relationship at propagation time based on cell strength.
5//!
6//! ```
7//! use adam_rs::{Method, Sheet};
8//! use std::any::TypeId;
9//! let mut sheet = Sheet::new();
10//! let a = sheet.add_source("prefix".to_string());
11//! let b = sheet.add_source('!');
12//! let raw = sheet.add_cell(String::new());
13//! let one = sheet.add_cell(String::new());
14//! let two = sheet.add_cell(String::new());
15//! sheet.add_relationship(vec![Method::new(vec![a], vec![raw],
16//! vec![TypeId::of::<String>()], vec![TypeId::of::<String>()],
17//! |v| Ok(vec![Box::new(v[0].downcast_ref::<String>().unwrap().clone())]))])?;
18//! sheet.add_relationship(vec![Method::from_fn_1_1(a, one, |v: &String| Ok(v.clone()))])?;
19//! sheet.add_relationship(vec![Method::from_fn_2_1([a, b], two,
20//! |v: &String, suffix: &char| Ok(format!("{v}{suffix}")))])?;
21//! sheet.propagate()?;
22//! assert_eq!(sheet.read::<String>(raw)?, "prefix");
23//! assert_eq!(sheet.read::<String>(one)?, "prefix");
24//! assert_eq!(sheet.read::<String>(two)?, "prefix!");
25//! # Ok::<(), adam_rs::Error>(())
26//! ```
27
28use std::any::{Any, TypeId};
29
30use slotmap::new_key_type;
31
32use crate::cell::CellId;
33
34new_key_type! {
35 /// A stable handle to a relationship in a [`crate::sheet::Sheet`].
36 ///
37 /// # Examples
38 /// See the [relationship example](crate::relationship).
39 pub struct RelationshipId;
40}
41
42/// Type-erased function stored inside a [`Method`].
43///
44/// Takes a slice of type-erased input references and returns a `Vec` of
45/// type-erased boxed outputs, or an error.
46type MethodFn = Box<dyn Fn(&[&dyn Any]) -> Result<Vec<Box<dyn Any>>, anyhow::Error>>;
47
48/// A single method within a relationship.
49///
50/// Its callback must be a pure, deterministic function of its input values and
51/// immutable captured constants, with no externally observable side effects.
52/// Equal inputs produce equivalent outputs or errors. There is no guarantee that
53/// the callback executes, or when or how often it executes.
54/// A self-referencing method must also be idempotent.
55/// Holding its other inputs fixed, substituting its outputs for overlapping inputs
56/// and applying it again produces equivalent outputs.
57///
58/// Every method in a relationship must conform the same constraint and preserve
59/// an assignment that already satisfies it. The runtime checks shared cell sets,
60/// not this semantic consistency.
61///
62/// # Examples
63/// See the [relationship example](crate::relationship).
64pub struct Method {
65 pub(crate) inputs: Vec<CellId>,
66 pub(crate) outputs: Vec<CellId>,
67 pub(crate) input_types: Vec<TypeId>,
68 pub(crate) output_types: Vec<TypeId>,
69 pub(crate) function: MethodFn,
70}
71
72impl Method {
73 /// Creates a method from explicit TypeIds and a type-erased function.
74 ///
75 /// - Precondition: `inputs.len() == input_types.len()` and `outputs.len() == output_types.len()`.
76 /// - Precondition: `f` satisfies [`Method`]'s purity and idempotence contract.
77 ///
78 /// Propagation validates the callback's output count and concrete types before
79 /// publishing staged values. Declared output order determines positional binding.
80 /// - Complexity: O(1) storage work, excluding ownership transfer of declared edges.
81 ///
82 /// # Examples
83 /// See the [relationship example](crate::relationship).
84 pub fn new<F>(
85 inputs: Vec<CellId>,
86 outputs: Vec<CellId>,
87 input_types: Vec<TypeId>,
88 output_types: Vec<TypeId>,
89 f: F,
90 ) -> Self
91 where
92 F: Fn(&[&dyn Any]) -> Result<Vec<Box<dyn Any>>, anyhow::Error> + 'static,
93 {
94 debug_assert_eq!(inputs.len(), input_types.len());
95 debug_assert_eq!(outputs.len(), output_types.len());
96 Method {
97 inputs,
98 outputs,
99 input_types,
100 output_types,
101 function: Box::new(f),
102 }
103 }
104
105 /// Creates a 1-input, 1-output method from a typed closure.
106 ///
107 /// TypeIds for `A` and `B` are captured automatically. The method is validated
108 /// against its cell registrations when passed to [`crate::sheet::Sheet::add_relationship`].
109 ///
110 /// - Precondition: `f` satisfies [`Method`]'s purity and idempotence contract.
111 ///
112 /// # Examples
113 /// See the [relationship example](crate::relationship).
114 pub fn from_fn_1_1<A, B, F>(input: CellId, output: CellId, f: F) -> Self
115 where
116 A: Any + 'static,
117 B: Any + 'static,
118 F: Fn(&A) -> Result<B, anyhow::Error> + 'static,
119 {
120 Method {
121 inputs: vec![input],
122 outputs: vec![output],
123 input_types: vec![TypeId::of::<A>()],
124 output_types: vec![TypeId::of::<B>()],
125 function: Box::new(move |args| {
126 let a = args[0]
127 .downcast_ref::<A>()
128 .expect("type checked at add_relationship");
129 Ok(vec![Box::new(f(a)?)])
130 }),
131 }
132 }
133
134 /// Creates a 2-input, 1-output method from a typed closure.
135 ///
136 /// `inputs[0]` maps to `A` and `inputs[1]` maps to `B`. TypeIds are captured
137 /// automatically. The method is validated when passed to
138 /// [`crate::sheet::Sheet::add_relationship`].
139 ///
140 /// - Precondition: `f` satisfies [`Method`]'s purity and idempotence contract.
141 ///
142 /// # Examples
143 /// See the [relationship example](crate::relationship).
144 pub fn from_fn_2_1<A, B, C, F>(inputs: [CellId; 2], output: CellId, f: F) -> Self
145 where
146 A: Any + 'static,
147 B: Any + 'static,
148 C: Any + 'static,
149 F: Fn(&A, &B) -> Result<C, anyhow::Error> + 'static,
150 {
151 Method {
152 inputs: inputs.to_vec(),
153 outputs: vec![output],
154 input_types: vec![TypeId::of::<A>(), TypeId::of::<B>()],
155 output_types: vec![TypeId::of::<C>()],
156 function: Box::new(move |args| {
157 let a = args[0]
158 .downcast_ref::<A>()
159 .expect("type checked at add_relationship");
160 let b = args[1]
161 .downcast_ref::<B>()
162 .expect("type checked at add_relationship");
163 Ok(vec![Box::new(f(a, b)?)])
164 }),
165 }
166 }
167}
168
169/// Owns validated methods and their shared, duplicate-free adjacent cell set.
170pub(crate) struct RelationshipData {
171 pub(crate) methods: Vec<Method>,
172 /// The set of cells referenced by every method in this relationship — every method
173 /// references the same set (enforced by `Sheet::add_relationship`).
174 pub(crate) adj: Vec<CellId>,
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 /// Preserves heterogeneous input order and typed errors through each typed adapter.
182 #[test]
183 fn typed_adapters_preserve_order_and_errors() {
184 let mut sheet = crate::Sheet::new();
185 let a = sheet.add_cell(String::new());
186 let b = sheet.add_cell('!');
187 let c = sheet.add_cell(String::new());
188 let method = Method::from_fn_2_1([a, b], c, |a: &String, b: &char| Ok(format!("{a}{b}")));
189 let result = (method.function)(&[&"prefix".to_string(), &'!']).unwrap();
190 assert_eq!(result[0].downcast_ref::<String>().unwrap(), "prefix!");
191 let one = Method::from_fn_1_1(a, c, |_: &String| -> anyhow::Result<String> {
192 Err(std::io::Error::other("one").into())
193 });
194 let two = Method::from_fn_2_1(
195 [a, b],
196 c,
197 |_: &String, _: &char| -> anyhow::Result<String> {
198 Err(std::io::Error::other("two").into())
199 },
200 );
201 assert!(
202 (one.function)(&[&String::new()])
203 .unwrap_err()
204 .downcast_ref::<std::io::Error>()
205 .is_some()
206 );
207 assert!(
208 (two.function)(&[&String::new(), &'!'])
209 .unwrap_err()
210 .downcast_ref::<std::io::Error>()
211 .is_some()
212 );
213 }
214
215 #[test]
216 /// Checks that relationship identities satisfy the Copy bound.
217 fn relationship_id_is_copy() {
218 /// Accepts only carriers that implement Copy.
219 fn takes_copy<T: Copy>(_: T) {}
220 // RelationshipId must be Copy so it can be stored in adjacency Vecs cheaply.
221 // This test fails to compile if RelationshipId is not Copy.
222 takes_copy(RelationshipId::default());
223 }
224
225 #[test]
226 /// Checks preservation of an erased method's positional edges and types.
227 fn method_new_stores_types_and_cell_ids() {
228 use slotmap::SlotMap;
229
230 // Create real CellIds from a SlotMap to avoid using the default (null) key.
231 let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
232 let a = map.insert(());
233 let b = map.insert(());
234 let c = map.insert(());
235
236 let method = Method::new(
237 vec![a, b],
238 vec![c],
239 vec![TypeId::of::<i32>(), TypeId::of::<i32>()],
240 vec![TypeId::of::<i32>()],
241 |args| {
242 let x = args[0].downcast_ref::<i32>().unwrap();
243 let y = args[1].downcast_ref::<i32>().unwrap();
244 Ok(vec![Box::new(x + y)])
245 },
246 );
247
248 assert_eq!(method.inputs, vec![a, b]);
249 assert_eq!(method.outputs, vec![c]);
250 assert_eq!(
251 method.input_types,
252 vec![TypeId::of::<i32>(), TypeId::of::<i32>()]
253 );
254 assert_eq!(method.output_types, vec![TypeId::of::<i32>()]);
255
256 // Verify the stored function works.
257 let x: i32 = 3;
258 let y: i32 = 4;
259 let result = (method.function)(&[&x, &y]).unwrap();
260 assert_eq!(*result[0].downcast_ref::<i32>().unwrap(), 7);
261 }
262
263 #[test]
264 /// Checks unary method signature types and its result.
265 fn from_fn_1_1_stores_correct_type_ids() {
266 use slotmap::SlotMap;
267
268 let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
269 let a = map.insert(());
270 let b = map.insert(());
271
272 let method = Method::from_fn_1_1(a, b, |x: &i32| Ok(*x * 2));
273
274 assert_eq!(method.inputs, vec![a]);
275 assert_eq!(method.outputs, vec![b]);
276 assert_eq!(method.input_types, vec![TypeId::of::<i32>()]);
277 assert_eq!(method.output_types, vec![TypeId::of::<i32>()]);
278
279 let x: i32 = 5;
280 let result = (method.function)(&[&x]).unwrap();
281 assert_eq!(*result[0].downcast_ref::<i32>().unwrap(), 10);
282 }
283
284 #[test]
285 /// Checks binary method signature types and its result.
286 fn from_fn_2_1_stores_correct_type_ids() {
287 use slotmap::SlotMap;
288
289 let mut map: SlotMap<CellId, ()> = SlotMap::with_key();
290 let a = map.insert(());
291 let b = map.insert(());
292 let c = map.insert(());
293
294 let method = Method::from_fn_2_1([a, b], c, |x: &f64, y: &f64| Ok((*x) * (*y)));
295
296 assert_eq!(method.inputs, vec![a, b]);
297 assert_eq!(method.outputs, vec![c]);
298 assert_eq!(
299 method.input_types,
300 vec![TypeId::of::<f64>(), TypeId::of::<f64>()]
301 );
302 assert_eq!(method.output_types, vec![TypeId::of::<f64>()]);
303
304 let x: f64 = 2.0;
305 let y: f64 = 3.0;
306 let result = (method.function)(&[&x, &y]).unwrap();
307 assert_eq!(*result[0].downcast_ref::<f64>().unwrap(), 6.0);
308 }
309}