Skip to content

Commit 7bd63d2

Browse files
authored
Make apply lazy (#1317)
Fixes #1300
1 parent aed95e3 commit 7bd63d2

12 files changed

Lines changed: 318 additions & 134 deletions

File tree

.github/workflows/run-clojure-test-suite.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ jobs:
1919
with:
2020
path: clojure-test-suite
2121
repository: basilisp-lang/clojure-test-suite
22-
ref: feature/support-basilisp
2322
- uses: actions/setup-python@v5
2423
with:
2524
python-version: '3.13'

src/basilisp/core.lpy

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
(def ^{:doc "Create a list from the arguments."
2424
:arglists '([& args])}
2525
list
26-
(fn* list [& args] (if args args '())))
26+
(fn* list [& args] (if args (basilisp.lang.list/list args) '())))
2727

2828
(def
2929
^{:doc "If ``seq`` is a Seq, return the first element from ``seq``. If ``seq``
@@ -209,11 +209,17 @@
209209
(fn iterator-seq [it]
210210
(basilisp.lang.runtime/to-iterator-seq it)))
211211

212-
213212
(def ^{:doc "Apply function ``f`` to the arguments provided.
214213

215214
The last argument must always be coercible to a Seq. Intermediate
216-
arguments are not modified."
215+
arguments are not modified. The Seq in the last argument may be
216+
infinite if the called function ``f`` is lazy.
217+
218+
.. warning::
219+
220+
Applying arguments to Python functions (rather than Basilisp
221+
functions) will consume the argument Seq eagerly. Infinite
222+
Seqs are **not** supported for Python functions."
217223
:arglists '([f & args])}
218224
apply
219225
(fn apply [f & args]
@@ -231,7 +237,7 @@
231237
:arglists '([& seqs])}
232238
concat
233239
(fn concat [& seqs]
234-
(apply basilisp.lang.runtime/concat seqs)))
240+
(basilisp.lang.runtime/concat-from-seq seqs)))
235241

236242
(def
237243
^{:doc "Create a hash map from pairs of input arguments."
@@ -2840,11 +2846,13 @@
28402846
(cons (f (first coll)) (map f (rest coll))))))
28412847
([f coll & colls]
28422848
(lazy-seq
2843-
(when-let [coll (seq coll)]
2844-
(let [colls (map seq colls)]
2845-
(when (every? identity colls)
2846-
(cons (apply f (first coll) (map first colls))
2847-
(apply map f (rest coll) (map rest colls)))))))))
2849+
(let [step-elems (fn step-elems [colls]
2850+
(lazy-seq
2851+
(let [colls (map seq colls)]
2852+
(when (every? identity colls)
2853+
(cons (map first colls)
2854+
(step-elems (map rest colls)))))))]
2855+
(map #(apply f %) (step-elems (cons coll colls)))))))
28482856

28492857
(def ^{:doc "Return a vector of ``(f elem)`` for elements in ``coll``\\. More than one
28502858
collection may be supplied. If more than one collection is supplied, the
@@ -2878,10 +2886,8 @@
28782886
"Return a lazy sequence of the concatenated results of mapping ``f`` over ``colls``\\."
28792887
([f]
28802888
(comp (map f) cat))
2881-
([f coll]
2882-
(apply concat (map f coll)))
2883-
([f coll & colls]
2884-
(apply concat (apply map f coll colls))))
2889+
([f & colls]
2890+
(apply concat (apply map f colls))))
28852891

28862892
(defn filter
28872893
"Return a lazy sequence of elements from ``coll`` where ``(pred elem)`` returns a

src/basilisp/lang/compiler/generator.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,7 @@ def _var_ns_as_python_sym(name: str) -> str:
799799
_ATTRIB_FIELD_FN_NAME = _load_attr(f"{_ATTR_ALIAS}.field")
800800
_BASILISP_LOAD_CONSTANT_NAME = _load_attr(f"{_RUNTIME_ALIAS}._load_constant")
801801
_COERCE_SEQ_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}.to_seq")
802+
_UNWRAP_REST_ARGS_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._unwrap_rest_args")
802803
_BASILISP_FN_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._basilisp_fn")
803804
_FN_WITH_ATTRS_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._with_attrs")
804805
_BASILISP_TYPE_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._basilisp_type")
@@ -1707,7 +1708,7 @@ def __fn_args_to_py_ast(
17071708
value=ast.IfExp(
17081709
test=ast.Name(id=arg_name, ctx=ast.Load()),
17091710
body=ast.Call(
1710-
func=_NEW_LIST_FN_NAME,
1711+
func=_UNWRAP_REST_ARGS_FN_NAME,
17111712
args=[ast.Name(id=arg_name, ctx=ast.Load())],
17121713
keywords=[],
17131714
),
@@ -1738,6 +1739,8 @@ def __fn_args_to_py_ast(
17381739
def __fn_decorator(
17391740
arities: Iterable[int],
17401741
has_rest_arg: bool = False,
1742+
*,
1743+
max_fixed_arity: int,
17411744
) -> ast.Call:
17421745
return ast.Call(
17431746
func=_BASILISP_FN_FN_NAME,
@@ -1767,7 +1770,8 @@ def __fn_decorator(
17671770
),
17681771
ctx=ast.Load(),
17691772
),
1770-
)
1773+
),
1774+
ast.keyword(arg="max_fixed_arity", value=ast.Constant(max_fixed_arity)),
17711775
],
17721776
)
17731777

@@ -1875,6 +1879,7 @@ def __single_arity_fn_to_py_ast( # pylint: disable=too-many-locals
18751879
__fn_decorator(
18761880
(len(fn_args),),
18771881
has_rest_arg=method.is_variadic,
1882+
max_fixed_arity=node.max_fixed_arity,
18781883
)
18791884
],
18801885
(
@@ -1905,10 +1910,11 @@ def __multi_arity_dispatch_fn( # pylint: disable=too-many-arguments,too-many-lo
19051910
ctx: GeneratorContext,
19061911
name: str,
19071912
arity_map: Mapping[int, str],
1913+
*,
19081914
return_tags: Iterable[Node | None],
19091915
default_name: str | None = None,
19101916
rest_arity_fixed_arity: int | None = None,
1911-
max_fixed_arity: int | None = None,
1917+
max_fixed_arity: int,
19121918
meta_node: MetaNode | None = None,
19131919
is_async: bool = False,
19141920
) -> GeneratedPyAST[ast.expr]:
@@ -2085,6 +2091,7 @@ def fn(*args):
20852091
)
20862092
),
20872093
has_rest_arg=default_name is not None,
2094+
max_fixed_arity=max_fixed_arity,
20882095
)
20892096
],
20902097
)
@@ -3688,7 +3695,7 @@ def _const_val_to_py_ast(
36883695
`_const_node_to_py_ast`."""
36893696
try:
36903697
serialized = pickle.dumps(form)
3691-
except (pickle.PicklingError, RecursionError) as e:
3698+
except (pickle.PicklingError, RecursionError) as e: # pragma: no cover
36923699
# For types without custom "constant" handling code, we defer to pickle
36933700
# to generate a representation that can be reloaded from the generated
36943701
# byte code. There are a few cases where that may not be possible for one

0 commit comments

Comments
 (0)