Skip to content

Commit 39374d2

Browse files
authored
Fix Map to work on arbitrary expressions, not just lists (#68) (#69)
Map now decomposes any compound expression (e.g. Power, Plus, Times, FunctionCall) into its head and children, applies the function to each child, and reconstructs. This matches Wolfram Language behavior where Map[f, x^2] returns f[x]^f[2] rather than returning unevaluated.
1 parent 50ff9aa commit 39374d2

File tree

2 files changed

+62
-5
lines changed

2 files changed

+62
-5
lines changed

src/functions/list_helpers_ast/mapping.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,26 @@ pub fn map_ast(func: &Expr, list: &Expr) -> Result<Expr, InterpreterError> {
2727
Ok(Expr::Association(results?))
2828
}
2929
_ => {
30-
// Not a list or association, return unevaluated
31-
Ok(Expr::FunctionCall {
32-
name: "Map".to_string(),
33-
args: vec![func.clone(), list.clone()],
34-
})
30+
// For any compound expression, decompose into head + children,
31+
// apply func to each child, and reconstruct.
32+
// E.g. Map[f, Power[x, 2]] -> Power[f[x], f[2]]
33+
use crate::functions::expr_form::{ExprForm, decompose_expr};
34+
match decompose_expr(list) {
35+
ExprForm::Composite { head, children } => {
36+
let mapped: Result<Vec<Expr>, _> = children
37+
.iter()
38+
.map(|child| apply_func_ast(func, child))
39+
.collect();
40+
crate::evaluator::evaluate_function_call_ast(&head, &mapped?)
41+
}
42+
ExprForm::Atom(_) => {
43+
// Atomic expression, return unevaluated
44+
Ok(Expr::FunctionCall {
45+
name: "Map".to_string(),
46+
args: vec![func.clone(), list.clone()],
47+
})
48+
}
49+
}
3550
}
3651
}
3752
}

tests/interpreter_tests/list.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3023,6 +3023,48 @@ mod join_non_list {
30233023
);
30243024
}
30253025

3026+
#[test]
3027+
fn map_on_power_expression() {
3028+
// Map[f, x^2] applies f to each part of Power[x, 2]
3029+
assert_eq!(interpret("Map[f, x^2]").unwrap(), "f[x]^f[2]");
3030+
}
3031+
3032+
#[test]
3033+
fn map_on_function_call() {
3034+
// Map[f, g[a, b, c]] applies f to each argument of g
3035+
assert_eq!(
3036+
interpret("Map[f, g[a, b, c]]").unwrap(),
3037+
"g[f[a], f[b], f[c]]"
3038+
);
3039+
}
3040+
3041+
#[test]
3042+
fn map_on_plus_expression() {
3043+
// Map[f, x + y] applies f to each summand
3044+
assert_eq!(interpret("Map[f, x + y]").unwrap(), "f[x] + f[y]");
3045+
}
3046+
3047+
#[test]
3048+
fn map_on_atom_unevaluated() {
3049+
// Map on an atom should return unevaluated
3050+
assert_eq!(interpret("Map[f, x]").unwrap(), "Map[f, x]");
3051+
}
3052+
3053+
#[test]
3054+
fn map_recursive_user_function() {
3055+
// Regression test for issue #68: Map with user-defined recursive function
3056+
assert_eq!(
3057+
interpret(
3058+
"TrigSimplify[expr_] := expr /; AtomQ[expr]\n\
3059+
TrigSimplify[expr_] := expr /; Head[expr] === If\n\
3060+
TrigSimplify[expr_] := Map[TrigSimplify, expr]\n\
3061+
TrigSimplify[x^2]"
3062+
)
3063+
.unwrap(),
3064+
"x^2"
3065+
);
3066+
}
3067+
30263068
#[test]
30273069
fn map_at_operator_form() {
30283070
assert_eq!(

0 commit comments

Comments
 (0)