Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions mathics/builtin/functional/apply_fns_to_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,21 @@ def callback(level):
Map $f$ onto each element (denoted by 'level' here) at this level.
With exception for expr as Association, which is mapped on values only.
"""
if is_association and level.has_form("Rule", 2):
# TODO: This special behavior applies when the whole expression
# is of the form Association[__(Rule|RuleDelayed)], i.e., when
# the expression is a well-formatted Association expression.
# For example,
# `Map[F, Association[a->1,b->2, NotARule]`
# produces in WMA
# `Association[F[a->1], F[b->2], F[NotARule]`
# instead of
# `Association[a->F[1], b->F[2], F[NotARule]`]
#
# Fixing this would require a different implementation of this eval_ method.
#
if is_association and level.has_form(("Rule", "RuleDelayed"), 2):
Copy link
Member

@rocky rocky Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mistake wasn't caught in the existing tests. So, probably a test needs to be added here for the new features added.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is, what should we test? For the full behavior, I have in mind a bunch of tests, that in the current implementation would not pass. The example in the comment is one of them.
To match with the WMA behavior, this method should be rewritten, and probably it would be also be interesting to define a specific class (like ListExpression) to store valid Association expressions. None of this would be too complicated, but right now I do not have the time to handle it.
What I could do is to list the tests here, or just add the tests to a pytest module, and mark them as xfail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is, what should we test?

So you are saying that nothing in this PR right now benefits a user? There is promise, though, that one day it will?

For the full behavior, I have in mind a bunch of tests, that in the current implementation would not pass. The example in the comment is one of them. To match with the WMA behavior, this method should be rewritten, and probably it would be also be interesting to define a specific class (like ListExpression) to store valid Association expressions. None of this would be too complicated, but right now I do not have the time to handle it. What I could do is to list the tests here, or just add the tests to a pytest module, and mark them as xfail.

Yes, it is better to add xfail or commented-out tests now while the issue is fresh in your mind rather than do it later. Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is, what should we test?

So you are saying that nothing in this PR right now benefits a user? There is promise, though, that one day it will?

For the full behavior, I have in mind a bunch of tests, that in the current implementation would not pass. The example in the comment is one of them. To match with the WMA behavior, this method should be rewritten, and probably it would be also be interesting to define a specific class (like ListExpression) to store valid Association expressions. None of this would be too complicated, but right now I do not have the time to handle it. What I could do is to list the tests here, or just add the tests to a pytest module, and mark them as xfail.

Yes, it is better to add xfail or commented-out tests now while the issue is fresh in your mind rather than do it later. Thanks.

This PR improves handling of some typical cases, but to cover the most general situation, we need to reimplement this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the full behavior, I have in mind a bunch of tests, that in the current implementation would not pass. The example in the comment is one of them. To match with the WMA behavior, this method should be rewritten, and probably it would be also be interesting to define a specific class (like ListExpression) to store valid Association expressions. None of this would be too complicated, but right now I do not have the time to handle it. What I could do is to list the tests here, or just add the tests to a pytest module, and mark them as xfail.

Yes, it is better to add xfail or commented-out tests now while the issue is fresh in your mind rather than do it later. Thanks.

This PR improves handling of some typical cases, but to cover the most general situation, we need to reimplement this.

It occurs to me that it might be good to open an issue with this information, so we don't forget after this PR is merged. Thanks.

return Expression(
SymbolRule,
level.get_head(),
level.elements[0],
Expression(f, level.elements[1]),
)
Expand Down
4 changes: 2 additions & 2 deletions mathics/eval/functional/apply_fns_to_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ def map_at_replace_one(elements: Iterable, index: ListExpression, i: int) -> lis
raise PartRangeError
new_elements = list(elements)
replace_element = elements[j]
if hasattr(replace_element, "head") and replace_element.head is SymbolRule:
if replace_element.has_form(("Rule", "RuleDelayed"), 2):
new_elements[j] = Expression(
SymbolRule,
replace_element.get_head(),
replace_element.elements[0],
Expression(f, replace_element.elements[1]),
)
Expand Down
72 changes: 72 additions & 0 deletions test/builtin/list/test_association.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,75 @@ def test_associations_private_doctests(
failure_message=assert_message,
expected_messages=expected_messages,
)


@pytest.mark.parametrize(
("str_expr", "expected_messages", "str_expected", "assert_message"),
[
(
"Map[F, Q[a->1, b:>Association[p->3,q->4]]]",
None,
"Q[F[a->1], F[b:>Association[p->3, q->4]]]",
"Acting on a nested association, the inner association is treated as normal.",
),
(
"Map[F, Q[a->1, b:>Association[p->3,q->4]],{2}]",
None,
"Q[F[a]->F[1], F[b]:>F[Association[p->3, q->4]]]",
"Acting on a nested association, the inner association is treated as normal.",
),
(
"Map[F, Association[a->1, b:>Association[p->3,q->4]], {0}]",
None,
"F[Association[a->1, b:>Association[p->3, q->4]]]",
"Special behavior occurs at the first level.",
),
(
"Map[F, Association[a->1, b:>2]]",
None,
"Association[a->F[1], b:>F[2]]",
"Over associations, Map acts on the values",
),
(
"Map[F, Association[a->1, b:>Association[p->3,q->4]]]",
None,
"Association[a->F[1], b:>F[Association[p->3, q->4]]]",
"Acting on a nested association, the inner association is treated as normal.",
),
(
"Map[F, Association[a->1, b:>Association[p->3,q->4]], {1}]",
None,
"Association[a->F[1], b:>F[Association[p->3, q->4]]]",
"Special behavior occurs at the first level.",
),
# FIXME
(
"Map[F, Association[a->1,b:>2,q]]",
None,
"Association[F[a->1], F[b:>2], F[q]]",
"Acting on an invalid association expression, works as in a normal expression.",
),
(
"Map[F, Association[a->1, b:>Association[p->3,q->4]], {2}]",
None,
"Association[a->1, b:>Association[F[p->3],F[q->4]]]",
"Special behavior occurs at the first level.",
),
(
"Map[F, Association[a->1, b:>Q[p->3, q->4]], {2}]",
None,
"Association[a->1, b:>Q[F[p->3],F[q->4]]]",
"Special behavior occurs at the first level.",
),
],
)
@pytest.mark.xfail
def test_map_over_associations(
str_expr, expected_messages, str_expected, assert_message
):
check_evaluation(
str_expr,
str_expected,
failure_message=assert_message,
expected_messages=expected_messages,
)
Loading