Questions about `ExprKind`
I don't quite understand the structure of ExprKind.
- why do I need
ExprKind<E = Expr>, canEbe any other structure? - why not use wrap type
Numberdirectly. - Can other fields be added? eg.
NumbericArray,Association
I mean something like this:
pub enum ExprKind {
Number(Number),
String(String),
Symbol(Symbol),
Normal(Normal),
NumbericArray(NumbericArray),
Association(Association),
}
Some possible base types
| Rust | Wolfram |
|---|---|
| Number(u8) | Integer / "Integer8" |
| Number(i8) | Integer / "UnsignedInteger8" |
| Number(BigInt) | Integer |
| String | String |
| Symbol | Symbol |
| Boolean | Symbol / "True" / "False" |
| List(VecDeque) | List |
| Association(IndexMap) | Association |
| NumericArray | NumericArray |
why do I need ExprKind<E = Expr>, can E be any other structure?
This dates back to 3fdd73d, which added an ArcExpr alternative to Expr. The problem was (and still is) that Expr uses an std::rc::Rc type for doing reference counting, but Rc's cannot be shared between threads. The ArcExpr type used the std::sync::Arc type instead and was intended to be a thread-safe alternative to Expr.
In theory this distinction is still meaningful, but I was never quite certain that the Expr vs ArcExpr dichotomy was the optimal design, so I removed it before doing the first official public release of wolfram-expr, with the hope that a better solution could be found in the future. (For example, it may be that simply making Expr use Arc instead of Rc would be a reasonable ergonomic and performance tradeoff. But I think more real-world usage experience would help make that determination.)
why not use wrap type Number directly.
-
Numberis a bit abstract for a type geared towards conversion between Rust and WL, where the concrete detail of what type of number it is would be difficult to abstract over.
- My intuition is that the cases where the programmer would want to distinguish the
Numbercase in-and-of-itself (and not care which type of number it is specifically) are far less common than the cases where the programmer would want to specifically check if the expression is e.g a machine integer or a machine real. There wouldn't be much ergonomic benefit to having aNumbervariant if the very next statement in the program would typically be anothermatchstatement over the variants of thatNumber.
- The variants of
ExprKindare intended to roughly line up with the basic 'primitive' types that are distinguishable in Wolfram Language.
At a practical level, the ergonomic consideration of (1) is code like:
let x: i64.= match expr {
ExprKind::Integer(x) => x,
_ => todo!()
};
versus:
let x: i64 = match expr {
ExprKind::Number(Number::Integer(x)) => x,
_ => todo!()
};
The second case is slightly more verbose, which could add up significantly when writing larger programs that deconstruct Expr instances in many places.
Can other fields be added? eg. NumericArray, Association
I think it's plausible that additional "validated" variants could be added to ExprKind in the future, though I think that bar is relatively high, and would require more experience with a specific design of a stand-alone Association or NumericArray type instance first.
A hypothetical ExprKind::Rational(Integer, Integer) is another potential variant that seems potentially compelling.
I think using api liketry_as_i64 may better than using match xxx, these wrapping methods can eliminate structural differences.
Nested structural easier to implement features like serde.