~technomancy/fennel

Table macros

Details
Message ID
<87r1zyrhwm.fsf@hagelb.org>
DKIM signature
missing
Download raw message
One common pain point with Fennel is that it's missing certain helper
functions you'd expect around tables, like map/filter/etc. This is done
because a key design goal of Fennel is to have no runtime overhead, so
introducing new functions is not possible.

However, this doesn't mean that we can't add them as macros; it would
just turn into a `let`+`each` form at compile-time.

We recently introduced `map` and `kvmap` to the compiler internals,
and it's helped clean up a fair bit of code. We could also expose it
in Fennel code itself, although it would have to me a macro which
would expand to an `each` call because of the restriction of having no
runtime.

The `map` function is simple:

    (map [1 2 3] (fn [x] (+ x 2)))

This uses ipairs to loop over the table in question and always returns a
sequential table. The arguments are backwards from most lisps, but
consistent with Lua's `table` functions which always take the table
first. I'm a little bit iffy about whether this should be added, because
as a built-in macro it would not be allowed to shadow it, and I'd
imagine wanting to have a local called `map` is pretty common. Plus most
lispers coming in from another language would be frustrated by the args
being backwards from the map function in every other lisp.

The `kvmap` function is different. The mapped function should take
both the key and the value as arguments. We could make it so that if
the function returns two values, they're taken as a key and value to
be put in the output table, but if it returns one value, then it's put
in using `table.insert`. With this behavior, `kvmap` can be used as a
`filter` function. It's also a superset of `map`.

Sometimes you don't have a table but just an iterator; for instance, if
you want to map over (string.gmatch mystr pattern). We could also
introduce an `imap` macro for going over iterators. This is much less
common, but it's also the most general; `kvmap` is just a special case
of `imap` where the iterator is always `pairs` of its arg. But needing
to use raw iterators is somewhat uncommon, and you can always fall back
to `each`.

In addition, these macros could use `#(. $1 ,f)` as their mapped
function when provided with a keyword or number as the "function".

Thoughts? My first inclination is to introduce `kvmap` as "alpha,
subject to change" in 0.4.0.

-Phil