Skip to content

Implement ArrayVar operations: filter, reduce, flat_map, foreach -> map#6701

Open
masenf wants to merge 1 commit into
mainfrom
masenf/filter-reduce
Open

Implement ArrayVar operations: filter, reduce, flat_map, foreach -> map#6701
masenf wants to merge 1 commit into
mainfrom
masenf/filter-reduce

Conversation

@masenf

@masenf masenf commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

implements Array -> new Array ops as var operations on ArrayVar

renames .foreach to .map for consistency with js and python

implements Array -> new Array ops as var operations on ArrayVar

renames `.foreach` to `.map` for consistency with js and python
@masenf masenf requested review from a team and Alek99 as code owners July 2, 2026 20:29
@codspeed-hq

codspeed-hq Bot commented Jul 2, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing masenf/filter-reduce (6c17367) with main (4c63f00)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR implements filter, reduce, and flat_map operations on ArrayVar and renames foreach to map (keeping foreach as a deprecated alias). The JavaScript helper pyFlatMap in state.js correctly mirrors Python's iteration semantics. Both unit and integration tests are comprehensive.

Confidence Score: 4/5

Safe to merge if the project's minimum Python version is 3.9+; verify before landing to avoid a runtime crash in reduce.

The implementation is clean and comprehensive with strong test coverage. The primary concern is the runtime subscript of collections.abc.Callable in reduce, which would raise TypeError on Python 3.8. The get_args shape ambiguity on older interpreters silently degrades type inference but does not affect correctness. Everything else — JS semantics, deprecation strategy, and documentation — is solid.

packages/reflex-base/src/reflex_base/vars/sequence.py — the Callable[[...], ...] subscript in reduce and the get_args handling in reduce_array_operation.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/vars/sequence.py Core change: refactors foreach into map, extracts _element_placeholder/_trace_element_fn helpers, and adds filter, reduce, and flat_map on ArrayVar. Reduce's return-type inference uses Callable from collections.abc whose subscript syntax requires Python 3.9+.
packages/reflex-base/src/reflex_base/.templates/web/utils/state.js Adds pyFlatMap helper emulating Python iteration semantics: arrays yield elements, strings yield characters, objects yield keys, non-iterables throw TypeError.
packages/reflex-components-core/src/reflex_components_core/core/upload.py Mechanical rename of foreach → map; no behaviour change.
packages/reflex-components-radix/src/reflex_components_radix/themes/components/segmented_control.py Mechanical rename of foreach → map; no behaviour change.
tests/units/test_var.py Adds unit tests for map, filter, reduce, and flat_map; verifies JS expression shape, types, imports, deprecation warning, and error paths.
tests/integration/test_var_operations.py Adds integration tests for all new operations end-to-end with expected rendered values; verifies the foreach deprecation alias.
docs/vars/var-operations.md Adds clear documentation section for map, filter, reduce, and flat_map with an executable demo.

Reviews (1): Last reviewed commit: "Implement ArrayVar operations: filter, r..." | Re-trigger Greptile

Comment on lines +478 to +509
if TYPE_CHECKING:

@deprecated("Use `ArrayVar.map` instead.")
def foreach(self, fn: Any) -> Var[list[Any]]:
"""Apply a function to each element of the array (deprecated, use map).

Args:
fn: The function to apply, taking at most one argument.

Returns:
The array after applying the function.
"""
...

first_arg_type = self[number_var]._var_type
else:

arg_name = get_unique_variable_name()
def foreach(self, fn: Any):
"""Apply a function to each element of the array (deprecated, use map).

# get first argument type
first_arg = Var(
_js_expr=arg_name,
_var_type=first_arg_type,
).guess_type()
Args:
fn: The function to apply, taking at most one argument.

function_var = ArgsFunctionOperation.create(
(arg_name,),
Var.create(fn(first_arg)),
Returns:
The array after applying the function.
"""
console.deprecate(
feature_name="ArrayVar.foreach",
reason="Use ArrayVar.map instead.",
deprecation_version="0.9.7",
removal_version="1.0",
)
return self.map(fn)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Unconventional class-body if TYPE_CHECKING split

Defining the same method in two branches of an if TYPE_CHECKING: block inside a class body is valid Python but unusual enough to be a maintenance trap. The runtime branch already calls console.deprecate, so the only thing the TYPE_CHECKING stub adds is the static-analysis decorator. A single method with typing_extensions.deprecated applied unconditionally (it is a no-op at runtime unless __deprecated__ is inspected) would be simpler and equally effective.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +572 to +578
function_var = ArgsFunctionOperation.create(
(accumulator._js_expr, element._js_expr),
return_expr,
_var_type=Callable[
[accumulator_type, element._var_type], return_expr._var_type
],
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Callable subscript is a Python 3.9+ feature when imported from collections.abc

Callable[[accumulator_type, element._var_type], return_expr._var_type] uses collections.abc.Callable (imported at the top of the file). Subscripting collections.abc.Callable directly requires Python 3.9+; on 3.8 it raises TypeError: 'ABCMeta' object is not subscriptable. If the project still targets Python 3.8, this line would crash whenever reduce is called. Using typing.Callable instead would be safe across all supported versions.

Suggested change
function_var = ArgsFunctionOperation.create(
(accumulator._js_expr, element._js_expr),
return_expr,
_var_type=Callable[
[accumulator_type, element._var_type], return_expr._var_type
],
)
import typing as _typing
function_var = ArgsFunctionOperation.create(
(accumulator._js_expr, element._js_expr),
return_expr,
_var_type=_typing.Callable[
[accumulator_type, element._var_type], return_expr._var_type
],
)

Comment on lines +2019 to +2038
return_type = callable_args[-1] if callable_args else Any
if initial is not None:
return var_operation_return(
js_expression=f"{array}.reduce({function}, {initial})",
var_type=unionize(return_type, initial._var_type),
)
param_types = callable_args[0] if callable_args else None
element_type = (
param_types[1]
if isinstance(param_types, list) and len(param_types) == 2
else Any
)
return var_operation_return(
js_expression=f"{array}.reduce({function})",
var_type=unionize(return_type, element_type),
)


_PY_FLAT_MAP_IMPORT: ImportDict = {
f"$/{Dirs.STATE_PATH}": [ImportVar(tag="pyFlatMap")],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 get_args shape differs between Python versions, silently falling back to Any

typing.get_args(Callable[[A, B], R]) returns ([A, B], R) on Python 3.9+ but (A, B, R) on Python 3.8. The guard isinstance(param_types, list) only matches the 3.9+ shape; on 3.8, element_type silently becomes Any, so the inferred return type of reduce without an initial value is Any rather than the correct union. If Python 3.8 is still supported this is a silent type-quality regression; if it isn't, a comment clarifying the assumption would help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant