Looking at the implementation of filterIt from the sequtils module I decided to give it a shot to implement equivalent templates for a hypothetical mapIt template. Here's what I tried:
template mapIt*(seq1, typ, pred: expr): expr {.immediate.} =
var result {.gensym.}: seq[typ] = @[]
for it {.inject.} in items(seq1):
result.add(pred)
result
template mapVit*(varSeq, pred: expr): expr {.immediate.} =
for i in 0 .. <len(varSeq):
let it {.inject.} = varSeq[i]
varSeq[i] = pred
when isMainModule:
var nums = @[1, 2, 3, 4]
let s = mapIt(nums, string, $it)
echo repr(s)
mapVit(nums, it * 3)
echo repr(nums)
Now this works, so it may be eligible for a pull request (where, into system.nim?), but it is less than ideal. In the first case of an immutable map the type has to be added to the parameters so that the type of the result sequence can be specified (using type didn't work, as it gets an empty type).
But the second mutable mapIt is even worse: I had expected that the different number of arguments would help the compiler pick the immutable or the mutable version, but it doesn't (Error: wrong number of arguments) so I had to rename it to mapVit, where V stands for var.
Any ideas how to improve this?
I believe there's currently a bug in the compiler where overloading resolution is disabled for immediate templates/macros (I guess because they're evaluated before the necessary information is available). I ran into it with an immediate macro with varargs (which was ignored, but worked when I wrote a non-immediate version). As I recall, there's even a comment in the compiler noting this, so it's probably a known issue.
I'd name the second version mapItInPlace or something similar, anyway, to make sure that programmers who use it (and maintainers who read code using it) know that the version employs a destructive update.
I'm not sure about the mapItInPlace, the mutable version doesn't return a value so you can't assign it to anything, plus it mimics the original interface which also uses an overload. Maybe the mutable version should be renamed mangle instead of map?
Voices in my head (or was it IRC?) helped me remove the {.immediate.} and suddenly everything is working. Here's the result, and it looks quite good:
template mapIt*(seq1, typ, pred: expr): expr =
var result {.gensym.}: seq[typ] = @[]
for it {.inject.} in items(seq1):
result.add(pred)
result
template mapIt*(varSeq, pred: expr) =
for i in 0 .. <len(varSeq):
let it {.inject.} = varSeq[i]
varSeq[i] = pred
when isMainModule:
var nums = @[1, 2, 3, 4]
let s = nums.mapIt(string, $(4 * it))
echo repr(s)
nums.mapIt(it * 3)
echo repr(nums)
So now it only needs a good place to live in, the crowded system.nim? I wonder if map should move to sequtils.nim like filter.