Hey folks! Haven't used nim in a while and I am a little bit confused as to why the following snippet doesn't work.
import tables
import math
let table = {"foo": 1, "bar": 2}.toTable
echo sum(table.values)
➜ nim c -r nim_iterator_problem.nim
Hint: used config file '/usr/local/Cellar/nim/1.4.2/nim/config/nim.cfg' [Conf]
Hint: used config file '/usr/local/Cellar/nim/1.4.2/nim/config/config.nims' [Conf]
..........
/Users/chris/src/aoc 2020/nim_iterator_problem.nim(5, 15) Error: undeclared field: 'values'
found 'tables.values(t: OrderedTableRef[values.A, values.B]) [declared in /usr/local/Cellar/nim/1.4.2/nim/lib/pure/collections/tables.nim(2154, 10)]' of kind 'iterator'
found 'tables.values(t: CountTable[values.A]) [declared in /usr/local/Cellar/nim/1.4.2/nim/lib/pure/collections/tables.nim(2565, 10)]' of kind 'iterator'
found 'tables.values(t: OrderedTable[values.A, values.B]) [declared in /usr/local/Cellar/nim/1.4.2/nim/lib/pure/collections/tables.nim(1746, 10)]' of kind 'iterator'
found 'tables.values(t: CountTableRef[values.A]) [declared in /usr/local/Cellar/nim/1.4.2/nim/lib/pure/collections/tables.nim(2841, 10)]' of kind 'iterator'
found 'tables.values(t: Table[values.A, values.B]) [declared in /usr/local/Cellar/nim/1.4.2/nim/lib/pure/collections/tables.nim(726, 10)]' of kind 'iterator'
found 'tables.values(t: TableRef[values.A, values.B]) [declared in /usr/local/Cellar/nim/1.4.2/nim/lib/pure/collections/tables.nim(1170, 10)]' of kind 'iterator'
All I want to do is sum the values of a table.
Of course the following works, but is there an idiomatic way of doing this without using a for-loop?
var sum = 0
for val in table.values:
sum += val
echo sum
Thanks!
I could swear I tried this variation, but maybe I just got too confused after
echo sum(table.values.toSeq)
gave the same error.That's an error I've stumbled upon too, the docs actually mention this but it's one of those things I just forget after reading: https://nim-lang.org/0.11.0/sequtils.html#toSeq.t,expr
Note: Since this is an immediate macro, you cannot always invoke this as x.toSeq, depending on the x. See this for an explanation.
sum from math works on openArrays, but what you have supplied is an iterator. Unfortunately, the support for iterator adapters/consumers is poor in Nim at the moment.
It seems that the idiomatic way is collecting to a sequence and then summing (or, in other cases, acting on the values of the new collection) which is conceptually looks like a hack and is a tradeoff, when compared to a simple loop.
Until a proper supports lands in the language, I get by with zero_functional lib, which allows the following syntax. The lib is not without its own quirks and limitations, though, but it works rather well for basic stuff.
import tables, zero_functional
let table = {"foo": 1, "bar": 2, "baz": 3}.toTable
echo table.values --> drop(1).map(it * 2).sum()
You could also roll your own one pass template thing easily enough:
import tables
let tab = {"foo": 1, "bar": 2}.toTable
template foldl(itr, updateAwithB, first): untyped =
var a {.inject.} = first
for b {.inject.} in itr: updateAwithB
a
echo foldl(tab.values, a += b, 0)
sequtils.foldl does not quite work this way (it wants items(tab.values) to work which does not).Try this
import tables, math, sequtils
let table = {"foo": 1, "bar": 2}.toTable
proc values*[K, V](table: Table[K, V]): seq[V] =
for k in table.values: result.add k
echo table.values.sum
Would be better if iterators wolud work differently, not how it works now - by breaking common ways to do simple things.
Please, don't roll your own procedures/templates, shadowing the regular and expected stdlib methods/iterators, unless you have no intentions to share your code ever.
Also, I don't think renaming values is a good idea:
Please, don't roll your own procedures/templates, shadowing the regular and expected stdlib methods/iterators, unless you have no intentions to share your code ever.
That seems like an unnecessarily strict rule. The Nim compiler will squawk at you whenever there is an ambiguity.
More generally, the way you write sounds more like Python guidance. (E.g., Nim uses "proc" for what you seem to call "method" and "method" for something else.)
Sure, but at the run-time cost of both allocating and two passes.
I haven't tested this, but there might also be a way to make a closure iterator and wrap it in toItr(that), but then you'd have a function call per summand which would be like 10-15x slower (but one pass & non-allocating, if it even works). (dotCalls for for-loop macros do not currently work yet, either, but I am not sure that is as fundamental a limitation as the other case.)
There may just not be a way to please everyone here. :-(
Sure, but at the run-time cost of both allocating and two passes.
I know, but in my opinion clean and readable code is more important than the performance.
As far as I know - there's no need for top performance everywhere, it's important to be able to write high-performant code when you need it - in the bottleneck. Use plain for-loop in the 2-3% codebase of the bottleneck. And enjoy simple and clean code in the rest 97% of the codebase (with "both allocating and two passes" - but who cares, it's not the bottleneck).
That's exactly what table.values from STD lib does in this case - breaking the expectations that method chaining table.values.sum would work.
No, it doesn't. The documentation, being as sparse as it is, nevertheless clearly states that sum works on arrays, and (unfortunately) Nim has no genericity over containers and iterators and need specific procedures/functions for them (or macros). This is the current state of the language and is widely known, not some arcana. The facts are: I expected this to work at first, you do too, it's a limitation, the workarounds are there. My opinion: your shadowing values still breaks expectations, because it's a special case, unlike zero_functional, which aspires to provide a uniform solution to basic iterator workflows.
As far as I know - there's no need for top performance everywhere
We're talking about basic functionality of the standard library. It's exactly the place to provide top performance. As zero_functional shows, you can also enjoy simple and clear code at the same time, if you're open to one more dependency (merely compile-time one, as it's only macros).
Found an example which in my personal opinion is a good illustration to the naming convention.
Package nre has the findIter proc. No confusion, you can use find or the findIter, immediatelly clear what's going on and how to use it.
import nre
for match in "abcde".findIter(re"[bcd]"):
echo match