Skip to main content

bhc_types/
dyn_tensor.rs

1//! Dynamic tensor types for gradual shape adoption.
2//!
3//! This module provides types for working with tensors whose shapes are not
4//! known at compile time, enabling gradual adoption of shape-indexed types.
5//!
6//! ## Overview
7//!
8//! Shape-indexed tensors (`Tensor '[m, n] Float`) provide compile-time dimension
9//! checking, but sometimes shapes are only known at runtime. `DynTensor` bridges
10//! this gap by providing an existentially-quantified wrapper.
11//!
12//! ## Usage Pattern
13//!
14//! ```text
15//! -- Convert static to dynamic (always succeeds)
16//! toDynamic :: Tensor shape a -> DynTensor a
17//!
18//! -- Convert dynamic to static (may fail at runtime)
19//! fromDynamic :: KnownShape shape => DynTensor a -> Maybe (Tensor shape a)
20//!
21//! -- Example:
22//! processData :: DynTensor Float -> IO ()
23//! processData dyn = case fromDynamic @'[1024, 768] dyn of
24//!     Just tensor -> optimizedPath tensor  -- statically known shape
25//!     Nothing     -> fallbackPath dyn      -- dynamic shape handling
26//! ```
27//!
28//! ## Type Signatures
29//!
30//! ```text
31//! DynTensor :: * -> *
32//!
33//! toDynamic :: forall shape a. Tensor shape a -> DynTensor a
34//!
35//! fromDynamic :: forall shape a. KnownShape shape
36//!             => DynTensor a -> Maybe (Tensor shape a)
37//!
38//! withDynShape :: DynTensor a -> (forall shape. Tensor shape a -> r) -> r
39//!
40//! dynShape :: DynTensor a -> [Int]
41//! ```
42
43use crate::{Kind, Ty, TyCon, TyList, TyVar};
44use bhc_intern::Symbol;
45
46/// Creates the `DynTensor` type constructor.
47///
48/// `DynTensor :: * -> *`
49///
50/// This is an existentially-quantified tensor type that hides the shape:
51/// ```text
52/// data DynTensor a where
53///   MkDynTensor :: Tensor shape a -> DynTensor a
54/// ```
55#[must_use]
56pub fn dyn_tensor_tycon() -> TyCon {
57    TyCon::new(Symbol::intern("DynTensor"), Kind::star_to_star())
58}
59
60/// Creates a `DynTensor a` type.
61///
62/// # Example
63///
64/// ```ignore
65/// let dyn_float = dyn_tensor_of(float_ty);
66/// // Represents: DynTensor Float
67/// ```
68#[must_use]
69pub fn dyn_tensor_of(elem_ty: Ty) -> Ty {
70    Ty::App(Box::new(Ty::Con(dyn_tensor_tycon())), Box::new(elem_ty))
71}
72
73/// Creates the `ShapeWitness` type constructor.
74///
75/// `ShapeWitness :: [Nat] -> *`
76///
77/// A singleton type that reifies a type-level shape to runtime:
78/// ```text
79/// data ShapeWitness shape = ShapeWitness
80/// ```
81#[must_use]
82pub fn shape_witness_tycon() -> TyCon {
83    // ShapeWitness :: [Nat] -> *
84    let kind = Kind::Arrow(Box::new(Kind::nat_list()), Box::new(Kind::Star));
85    TyCon::new(Symbol::intern("ShapeWitness"), kind)
86}
87
88/// Creates a `ShapeWitness shape` type.
89#[must_use]
90pub fn shape_witness_of(shape: TyList) -> Ty {
91    Ty::App(
92        Box::new(Ty::Con(shape_witness_tycon())),
93        Box::new(Ty::TyList(shape)),
94    )
95}
96
97/// Creates the type for `toDynamic`.
98///
99/// ```text
100/// toDynamic :: forall shape a. Tensor shape a -> DynTensor a
101/// ```
102#[must_use]
103pub fn to_dynamic_type(tensor_tycon: &TyCon) -> Ty {
104    let shape_var = TyVar::new(0, Kind::nat_list());
105    let a_var = TyVar::new(1, Kind::Star);
106
107    // Tensor shape a
108    let tensor_type = Ty::App(
109        Box::new(Ty::App(
110            Box::new(Ty::Con(tensor_tycon.clone())),
111            Box::new(Ty::TyList(TyList::Var(shape_var.clone()))),
112        )),
113        Box::new(Ty::Var(a_var.clone())),
114    );
115
116    // DynTensor a
117    let dyn_tensor_type = dyn_tensor_of(Ty::Var(a_var.clone()));
118
119    // Tensor shape a -> DynTensor a
120    let fun_type = Ty::fun(tensor_type, dyn_tensor_type);
121
122    // forall shape a. Tensor shape a -> DynTensor a
123    Ty::Forall(vec![shape_var, a_var], Box::new(fun_type))
124}
125
126/// Creates the type for `fromDynamic`.
127///
128/// ```text
129/// fromDynamic :: forall shape a. ShapeWitness shape -> DynTensor a -> Maybe (Tensor shape a)
130/// ```
131///
132/// Note: In a full implementation, the `ShapeWitness` would be provided by
133/// a `KnownShape` type class constraint. Here we make it explicit.
134#[must_use]
135pub fn from_dynamic_type(tensor_tycon: &TyCon, maybe_tycon: &TyCon) -> Ty {
136    let shape_var = TyVar::new(0, Kind::nat_list());
137    let a_var = TyVar::new(1, Kind::Star);
138
139    // ShapeWitness shape
140    let witness_type = shape_witness_of(TyList::Var(shape_var.clone()));
141
142    // DynTensor a
143    let dyn_tensor_type = dyn_tensor_of(Ty::Var(a_var.clone()));
144
145    // Tensor shape a
146    let tensor_type = Ty::App(
147        Box::new(Ty::App(
148            Box::new(Ty::Con(tensor_tycon.clone())),
149            Box::new(Ty::TyList(TyList::Var(shape_var.clone()))),
150        )),
151        Box::new(Ty::Var(a_var.clone())),
152    );
153
154    // Maybe (Tensor shape a)
155    let maybe_tensor = Ty::App(
156        Box::new(Ty::Con(maybe_tycon.clone())),
157        Box::new(tensor_type),
158    );
159
160    // ShapeWitness shape -> DynTensor a -> Maybe (Tensor shape a)
161    let fun_type = Ty::fun(witness_type, Ty::fun(dyn_tensor_type, maybe_tensor));
162
163    // forall shape a. ...
164    Ty::Forall(vec![shape_var, a_var], Box::new(fun_type))
165}
166
167/// Creates the type for `withDynShape`.
168///
169/// ```text
170/// withDynShape :: forall a r. DynTensor a -> (forall shape. Tensor shape a -> r) -> r
171/// ```
172///
173/// This is a continuation-passing style function for working with dynamic shapes.
174#[must_use]
175pub fn with_dyn_shape_type(tensor_tycon: &TyCon) -> Ty {
176    let a_var = TyVar::new(0, Kind::Star);
177    let r_var = TyVar::new(1, Kind::Star);
178    let shape_var = TyVar::new(2, Kind::nat_list());
179
180    // DynTensor a
181    let dyn_tensor_type = dyn_tensor_of(Ty::Var(a_var.clone()));
182
183    // Tensor shape a
184    let tensor_type = Ty::App(
185        Box::new(Ty::App(
186            Box::new(Ty::Con(tensor_tycon.clone())),
187            Box::new(Ty::TyList(TyList::Var(shape_var.clone()))),
188        )),
189        Box::new(Ty::Var(a_var.clone())),
190    );
191
192    // forall shape. Tensor shape a -> r
193    let continuation = Ty::Forall(
194        vec![shape_var],
195        Box::new(Ty::fun(tensor_type, Ty::Var(r_var.clone()))),
196    );
197
198    // DynTensor a -> (forall shape. Tensor shape a -> r) -> r
199    let fun_type = Ty::fun(
200        dyn_tensor_type,
201        Ty::fun(continuation, Ty::Var(r_var.clone())),
202    );
203
204    // forall a r. ...
205    Ty::Forall(vec![a_var, r_var], Box::new(fun_type))
206}
207
208/// Creates the type for `dynShape`.
209///
210/// ```text
211/// dynShape :: forall a. DynTensor a -> [Int]
212/// ```
213///
214/// Returns the runtime shape of a dynamic tensor as a list of integers.
215#[must_use]
216pub fn dyn_shape_type(int_tycon: &TyCon) -> Ty {
217    let a_var = TyVar::new(0, Kind::Star);
218
219    // DynTensor a
220    let dyn_tensor_type = dyn_tensor_of(Ty::Var(a_var.clone()));
221
222    // [Int]
223    let int_list = Ty::List(Box::new(Ty::Con(int_tycon.clone())));
224
225    // DynTensor a -> [Int]
226    let fun_type = Ty::fun(dyn_tensor_type, int_list);
227
228    // forall a. DynTensor a -> [Int]
229    Ty::Forall(vec![a_var], Box::new(fun_type))
230}
231
232/// Creates the type for `dynRank`.
233///
234/// ```text
235/// dynRank :: forall a. DynTensor a -> Int
236/// ```
237#[must_use]
238pub fn dyn_rank_type(int_tycon: &TyCon) -> Ty {
239    let a_var = TyVar::new(0, Kind::Star);
240
241    // DynTensor a
242    let dyn_tensor_type = dyn_tensor_of(Ty::Var(a_var.clone()));
243
244    // DynTensor a -> Int
245    let fun_type = Ty::fun(dyn_tensor_type, Ty::Con(int_tycon.clone()));
246
247    // forall a. DynTensor a -> Int
248    Ty::Forall(vec![a_var], Box::new(fun_type))
249}
250
251/// Creates a static shape witness type from concrete dimensions.
252///
253/// # Example
254///
255/// ```ignore
256/// let witness = static_shape_witness(&[1024, 768]);
257/// // Represents: ShapeWitness '[1024, 768]
258/// ```
259#[must_use]
260pub fn static_shape_witness(dims: &[u64]) -> Ty {
261    let shape = TyList::shape_from_dims(dims);
262    shape_witness_of(shape)
263}
264
265/// Checks if a type is a `DynTensor` type.
266#[must_use]
267pub fn is_dyn_tensor(ty: &Ty) -> bool {
268    match ty {
269        Ty::App(f, _) => matches!(f.as_ref(), Ty::Con(c) if c.name.as_str() == "DynTensor"),
270        _ => false,
271    }
272}
273
274/// Extracts the element type from a `DynTensor a` type.
275#[must_use]
276pub fn dyn_tensor_elem_type(ty: &Ty) -> Option<&Ty> {
277    match ty {
278        Ty::App(f, elem) => {
279            if matches!(f.as_ref(), Ty::Con(c) if c.name.as_str() == "DynTensor") {
280                Some(elem)
281            } else {
282                None
283            }
284        }
285        _ => None,
286    }
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292
293    #[test]
294    fn test_dyn_tensor_tycon() {
295        let tycon = dyn_tensor_tycon();
296        assert_eq!(tycon.name.as_str(), "DynTensor");
297        assert_eq!(tycon.kind, Kind::star_to_star());
298    }
299
300    #[test]
301    fn test_shape_witness_tycon() {
302        let tycon = shape_witness_tycon();
303        assert_eq!(tycon.name.as_str(), "ShapeWitness");
304        match &tycon.kind {
305            Kind::Arrow(from, to) => {
306                assert_eq!(**from, Kind::nat_list());
307                assert_eq!(**to, Kind::Star);
308            }
309            _ => panic!("expected arrow kind"),
310        }
311    }
312
313    #[test]
314    fn test_dyn_tensor_of() {
315        let float_ty = Ty::Con(TyCon::new(Symbol::intern("Float"), Kind::Star));
316        let dyn_ty = dyn_tensor_of(float_ty.clone());
317
318        assert!(is_dyn_tensor(&dyn_ty));
319        assert_eq!(dyn_tensor_elem_type(&dyn_ty), Some(&float_ty));
320    }
321
322    #[test]
323    fn test_static_shape_witness() {
324        let witness = static_shape_witness(&[1024, 768]);
325        match &witness {
326            Ty::App(f, arg) => {
327                assert!(matches!(f.as_ref(), Ty::Con(c) if c.name.as_str() == "ShapeWitness"));
328                match arg.as_ref() {
329                    Ty::TyList(list) => {
330                        let dims = list.to_static_dims().unwrap();
331                        assert_eq!(dims, vec![1024, 768]);
332                    }
333                    _ => panic!("expected TyList"),
334                }
335            }
336            _ => panic!("expected App"),
337        }
338    }
339
340    #[test]
341    fn test_is_dyn_tensor() {
342        let float_ty = Ty::Con(TyCon::new(Symbol::intern("Float"), Kind::Star));
343        let dyn_ty = dyn_tensor_of(float_ty);
344        assert!(is_dyn_tensor(&dyn_ty));
345
346        let non_dyn = Ty::Con(TyCon::new(Symbol::intern("Int"), Kind::Star));
347        assert!(!is_dyn_tensor(&non_dyn));
348    }
349
350    #[test]
351    fn test_to_dynamic_type() {
352        let tensor_tycon = TyCon::new(Symbol::intern("Tensor"), Kind::tensor_kind());
353        let ty = to_dynamic_type(&tensor_tycon);
354
355        // Should be forall shape a. Tensor shape a -> DynTensor a
356        match ty {
357            Ty::Forall(vars, _body) => {
358                assert_eq!(vars.len(), 2);
359                assert_eq!(vars[0].kind, Kind::nat_list()); // shape
360                assert_eq!(vars[1].kind, Kind::Star); // a
361            }
362            _ => panic!("expected forall type"),
363        }
364    }
365
366    #[test]
367    fn test_from_dynamic_type() {
368        let tensor_tycon = TyCon::new(Symbol::intern("Tensor"), Kind::tensor_kind());
369        let maybe_tycon = TyCon::new(Symbol::intern("Maybe"), Kind::star_to_star());
370        let ty = from_dynamic_type(&tensor_tycon, &maybe_tycon);
371
372        // Should be forall shape a. ShapeWitness shape -> DynTensor a -> Maybe (Tensor shape a)
373        match ty {
374            Ty::Forall(vars, _body) => {
375                assert_eq!(vars.len(), 2);
376            }
377            _ => panic!("expected forall type"),
378        }
379    }
380}