map and filter

Native lazy iteration builtins. map applies a function to each element of a source; filter keeps the elements a predicate approves. Both accept any iterable – a sequence, a koskari generator, a python generator, or a python async iterator – and both can be consumed synchronously or asynchronously.

Unlike their python counterparts, these run inside the koskari evaluator: a koskari function passed as the mapper or predicate is driven directly, with no wrapping, and may await.

map

Syntax: (map FUNC ITERABLE ...)

Return a lazy iterator producing (FUNC element) for each element of ITERABLE. Nothing is consumed until the map itself is iterated.

(list (map (lambda [x] (* x 10)) #[1 2 3]))
; #[10 20 30]

With several iterables, FUNC takes one element from each per step, in the order the iterables are written, and the map ends with the shortest of them. The iterables are stepped left to right, so once one is spent the ones after it are not asked for that step.

(list (map + #[1 2 3] #[10 20 30]))
; #[11 22 33]

(list (map (lambda [a b] (#tuple a b)) #[1 2 3] "ab"))
; #[#(1 "a") #(2 "b")]

FUNC may be any callable: a koskari function, a python function, or another callable object. A koskari function that suspends works transparently when the map is consumed in an asynchronous context:

import-from asyncio sleep

def slow-double [x]:
    (sleep 0)
    (* x 2)

for-each [v (map slow-double #[1 2 3])]:
    (print v)

A koskari function awaits a suspending call such as sleep on its own; no await is written.

Matching python’s map, a mapped function that raises StopIteration truncates the iteration.

filter

Syntax: (filter PRED ITERABLE)

Return a lazy iterator producing the elements of ITERABLE for which (PRED element) is true. PRED may be None, keeping the truthy elements – the same convention as python’s filter.

(list (filter (lambda [x] (> x 1)) #[1 2 3]))
; #[2 3]

(list (filter None #[0 1 "" "x"]))
; #[1 "x"]

Like map, the predicate may be a koskari function and may await.

Consuming a map or filter

Both are lazy single-pass iterators, usable from either surface:

  • synchronously: (list ...), (next ...), for-each, or python list(), for and next(iter(m)). A map or filter is an iterable, not an iterator, so python next() needs the iter() step. A source or function that genuinely suspends on an awaitable cannot be driven this way and raises KoskariSyncViolation.

  • asynchronously: for-each in an asynchronous context, or python async for. Awaits in the function, the predicate, or the source pass through transparently.

Exhaustion is permanent: a drained map or filter yields nothing on later iteration. Closing one (python aclose, or generator close semantics) closes its source.

Type predicates

map? and filter? test for the native types:

(map? (map str #[1]))          ; True
(filter? (py-filter None #[1])) ; False

py-map and py-filter

The python builtins remain available as py-map and py-filter, with py-map? and py-filter? as their type predicates. See the python documentation for map and filter.

(list (py-map str #[1 2]))   ; #["1" "2"]
(py-map? (py-map str #[1]))  ; True

The python builtins call their function through the python calling convention. A koskari function used there behaves as it would in any python higher-order function: wrap it with demand so each call returns a completed value. The native map and filter need no wrapping.