Like sortIt() or applyIt() where we are forced to use the it variable name in the condition?
When I started with Nim some years ago I thought that that is the only possible way to do it in Nim -- but I always regard it as very ugly.
I just remembered that I tried successfully to use a custom variable name some time ago. I think it was something like this:
#The original Nim version:
template applyIt*(varSeq, op: untyped) =
for i in 0 .. <varSeq.len:
let it {.inject.} = varSeq[i]
varSeq[i] = op
var nums = @[1, 2, 3, 4]
nums.applyIt(it * 3)
assert nums[0] + nums[3] == 15
# the new version with custom name
template apply*(varSeq, el, op: untyped) =
for el in mitems(varSeq):
el = op
nums = @[1, 2, 3, 4]
nums.apply(el, el * 3)
assert nums[0] + nums[3] == 15
echo nums
Personally I regard the call nums.apply(el, el * 3) as much nicer. It looks more like the Ruby code, where we have for example
a = [ "a", "b", "c" ]
a.delete_if {|x| x >= "b" } #=> ["a"]
I have not yet investigated if other It templates like sortIt() can be rewritten in this way too, but I hope so.
I'm with Stefan here, those it seem to be dropped from nowhere and appear and disappear like magic.
For me lambdas are about reducing boilerplate yes, but also readability and composition.
It was non-trivial to understand where did the it in mapIt or the a and b in fold came from at first. Templates seemed like magic.
If you are using an untyped parameter in a macro, try the for construct:
mapIt(somemap, for e in pow(e, 2))
You're already used to reading "for a value which shall change at each step" so only the "in an expression" is being mentally overloaded. It's as close as we're going to get to Ruby without more syntax.
@Stefan_Salewski If you want something Ruby-like, why not use => from future?
Like this:
import future, sequtils
var nums = @[1, 2, 3, 4]
nums.apply(el => el * 3)
echo nums
@olwi, that creates a closure, if you use that in a loop it will be significantly slower than an inline template. See my bench in Arraymancer.
# Results with -d:release on i5-5257U (dual-core mobile 2.7GHz, turbo 3.1)
# Proc with ref object 0.099993
# Closures 2.708598
# Methods 0.3122219999999998
sequtils could use some overloads to accept this behavior witouth breaking the old one:
template apply*(varSeq, op: untyped) = apply(varSeq, it, op)
I tried this for benchmarking (edit: the web server doesn't do -d:release), but on my machine, they are all equivalement, just change due to the GC timings
import future, sequtils, times, math
const
LoopCnt = 10_000_000
mult = 1000
var
sq: seq[float] = newSeqWith(LoopCnt, 0.0)
t0 = epochTime()
proc setupSeq() =
for i in 0..<LoopCnt: sq[i] = i.float
proc useIt() =
setupSeq()
t0 = epochTime()
sq.applyIt(pow(it,2))
echo "applyIt: ", mult * (epochTime() - t0)
proc useClosure() =
setupSeq()
t0 = epochTime()
sq.apply(x => pow(x,2))
echo "closure: ", mult * (epochTime() - t0)
proc useLoop1() =
setupSeq()
t0 = epochTime()
for i in 0..<LoopCnt: sq[i] = pow(sq[i], 2)
echo "loop1: ", mult * (epochTime() - t0)
proc useLoop2() =
setupSeq()
t0 = epochTime()
for i in 0..<LoopCnt: sq[i] = sq[i] * sq[i]
echo "loop2: ", mult * (epochTime() - t0)
proc main() =
useIt()
useIt()
useClosure()
useClosure()
useLoop1()
useLoop1()
useLoop2()
useLoop2()
main()
e.g. in Kotlin, when a function with only one parameter is accepted, in it's definition, the first parameter name can be omitted and instead it's named it implicitly:
strings.filter { it.length == 5 }.sortedBy { it }.map { it.toUpperCase() }
So there it's even part of the core language. In Nim it's only a part of certain stdlib templates, but it shows that there are other places, where such implicit things are used.
Also the it is part of the template's name(mapIt, applyIt, ...).
@Araq Maybe you don't need flexibility but please imagine using a it-template inside of an it-template (e.g. an apply inside of an apply). :-/ Just like @olwi said, => is better and I think it should be brought to standard Nim.
People only mention two reasons to use it-templates, correct me if I'm wrong:
@doofenstein But it's optional (just like in, let's say, Scala). Why? Because of the higher-order-functions composition, which would make it (pun intended) ambiguous.
I do like the It templates, they make the code shorter.
Although I also found that nesting them was awkward when trying to multiply two sequences, it made me think of different ways to achieve the same thing, and make it look even better than the initial implementation, it ended looking like this:
let acc = neuron.weights.zip(input).mapIt(it.a * it.b).foldr(a + b)
What's not to like about the above code; without the It template, the map would end up being more verbose, or worse, using for loops.