Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sequtils.toSeq produces the sequence from the iterator twice if compiles(iter.len) == true #7187

Closed
skilchen opened this issue Feb 5, 2018 · 3 comments · Fixed by #8586 or #8666
Closed

Comments

@skilchen
Copy link
Contributor

skilchen commented Feb 5, 2018

In the doc comment for sequtils.toSeq we find this example:

let
  numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
  odd_numbers = toSeq(filter(numeric) do (x: int) -> bool:
         if x mod 2 == 1:
           result = true)

If you run this, the filtered sequence is produced twice, as can be seen here on the nim playground with my instrumented copies of filter and toSeq. I think its the same problem that was fixed eg. in mapIt by copying the iterable to a local variable and therefore forcing the iteration to take place.

My proposed fix looks like this:

template toSeqFixed*(iter: untyped): untyped =
  when compiles(iter.len):
    iter
  else:
    var result: seq[type(iter)] = @[]
    for x in iter:
      result.add(x)
    result

but, as usual, i am not sure if this fix is correct...

@skilchen skilchen changed the title sequtils.toSeq produces to sequence from the iterator twice if compiles(iter.len) == true sequtils.toSeq produces the sequence from the iterator twice if compiles(iter.len) == true Feb 5, 2018
@data-man
Copy link
Contributor

@skilchen
It's actual for the latest Nim?
Because of this I can't repeat:

If you run this, the filtered sequence is produced twice

Or I haven't figured out what the problem is, sorry.

@skilchen
Copy link
Contributor Author

@data-man yes it is still actual for the latest Nim.
compiles(iter.len) is only true if there are two implementations of iter: one as an iterator (here iter.len isn't compileable) and the other as procedure (here iter.len compiles). This is the case for filter but also for e.g. strutils.split.

Maybe it becomes clearer what happens, if you compile the following example and then inspect the generated C code:

import sequtils

proc test() =
  var a = "a,b,c"

  let s = toSeq(filter(a) do (x: char) -> bool: x != ',')
  echo s
  
test()

In the generated C code in nmcache you will then find an implementation of filter and test procedures both containing the same innermost loop.

Maybe a less exotic fix to this issue would be something like this:

template toSeq*(iter: untyped): untyped =
  when compiles(iter.len):
    var i = 0
    let t = iter
    var result = newSeq[type(iter)](t.len)
    for x in t:
      result[i] = x
      inc i
    result
  else:
    var result: seq[type(iter)] = @[]
    for x in iter:
      result.add(x)
    result

Important is the line let t = iter and then iterating over this t instead of iter. With this modified toSeq in the generated C code you will now see, that the inner loops in filter and test are no longer the same. test loops over the results of filter and doesn't inline the filter-iterator-loop a second time.
Unfortunately i don't master the jargon of metaprogramming, so i can not say what exactly happens in the line let t = iter, maybe something like: converting the untyped argument of the template to a concrete type...

@timotheecour
Copy link
Member

sent out fix here: #8586

timotheecour added a commit to timotheecour/Nim that referenced this issue Aug 16, 2018
Araq pushed a commit that referenced this issue Aug 16, 2018
PMunch pushed a commit to PMunch/Nim that referenced this issue Aug 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants