Hi!
There are some ideas about list comprehension improvemets. The first is about the type of the result. It's annoying to do the work that the compiler must do. So, I want to throw it away. Of cause, auto doesn't work and leads to standard error while trying to infer the type of the element of the sequence:
lctest.nim(4, 24) Error: A nested proc can have generic parameters only when it is used as an operand to another routine and the types of the generic paramers can be inferred from the expected signature.
And when I'm trying to find out the code, produced by the list comprehension macro from the future, I receive another error. Here it is: https://gist.github.com/vegansk/4ac21b238c4d4bf329da. Am I doing something wrong or is this a compiler bug?
The second idea is about replacing the sequence by the iterator. We can use sequtils.toSeq if we really need the sequence. And we can use some heavy computations in lc, they will be computed only if needed.
So, what do you think about it?
Figured out that the problem described in gist is a compiler issue, so here it is: https://github.com/nim-lang/Nim/issues/3991.
But the main question is how to get the type of the result for the list comprehension. We must enumerate all the parts of the lc which unfolds to cycles and determine their variable's types. And then we must inference the result type from theese. So, how can we get the type of the expression in macro? Is it possible?
I've brought this up before, but this is another reason why I'm not a huge fan of lc for list comprehensions and instead prefer to use a simple template in conjunction with iterators:
template enumerate*(s: untyped): untyped =
block:
iterator `~tmp`(): auto = s
var result = newSeq[type(`~tmp`())]()
for item in `~tmp`():
add(result, item)
result
(Though it is a bit awkward that you can't hide local variables from an untyped statement block and need to resort to identifier obfuscation.)
While this is more verbose than lc, I don't think the verbosity here is necessarily bad and it integrates well with the rest of the language. Importantly, type inference works.
proc main =
const n = 20
let triangles = enumerate do:
for x in 1..n:
for y in x..n:
for z in y..n:
if x*x + y*y == z*z:
yield (a: x, b: y, c: z)
let even10 = enumerate do:
for x in 1..10:
if x mod 2 == 0:
yield x
echo triangles
echo even10
main()
I don't think that would read nice:
let triangles = seqYielded do:
for x in 1..n:
for y in x..n:
for z in y..n:
if x*x + y*y == z*z:
yield (a: x, b: y, c: z)
I think in this context loop is less weird than in isolation:
let triangles = loop do:
for x in 1..n:
for y in x..n:
for z in y..n:
if x*x + y*y == z*z:
yield (a: x, b: y, c: z)
but enumerate is also fine
collect as a verb is fine - I don't care much about the verb. You could even go all the way to comprehend which might let people find it more easily with partial substring searches of comprehen* in theindex.html.
However, correctly indicating the output type in the name somehow just feels best to me. This is especially true here because the let/var type inference works well in Jehan's approach. So, there may be no other indication to the reader what type of collection is made. Maybe this approach could be extended to be able to do "table comprehensions" or "set comprehensions" or even singly- and doubly-linked list comprehensions from the stdlib's collections.lists. Then we would need like 5 names and seqCollect, tableCollect, etc. would all be a natural family. (In this light, lc/ListComprehension in future.nim is already poorly named - it should be seqComprehension since output is a Nim seq not a Haskell singly-linked list or Python array list.)
Alternately, one simpler name like collect could work, but maybe have users provide the output data structure as an argument to the template. Then maybe any collection type with an appropriate add proc would work with the construct automatically. Reusing/presizing the output might then also become a possibility.