I am trying to understand the problem depicted in this Stack Overflow post — implementing map min that takes the tables keys iterator as argument in nim.
The author of the post tries three attempts to define a map function that would work for tables. I guess there is a type error with its first attempt:
iterator map[A, B](iter: iterable[A], fn: A -> B): B =
for x in iter:
yield fn(x)
When I try its code, I have this:
import std/[sugar, tables]
# A mapping from coordinates (x, y) to values.
var locations = initTable[(int, int), int]()
locations[(1, 2)] = 1
locations[(2, 1)] = 2
locations[(-2, 5)] = 3
echo typeof(locations.keys)
echo locations.keys is (int, int)
echo locations.keys is iterable[(int, int)]
I get:
(int, int)
true
false
while I would expect:
iterable[(int, int)]
false
true
I don't understand why I don't have an iterable type there. Is this due to the lent return type ? See the prototype to the keys iterator:
iterator keys[A, B](t: Table[A, B]): lent A
(I did not find any mention about the lent return type in the manual, but got a description in destructors documentation which is not an obvious place to look for an unknown system type.)
Can we avoid this with another prototype in the standard library ? Like:
iterator keys[A, B](t: Table[A, B]): lent iterable[lent A]
I guess this is related to this type system "issue":
proc add(x: int): int = x + 2
echo type(add(2))
echo type(add)
returns
int
None
Why None ? Shouldn't this be proc (it: int): int ?Last time I checked, iterable was useless as it's designed to work only in templates.
Native iterators in Nim don't compose without macro-trickery, such as zero_functional. Actually, the only good thing I can say about them is when they do work, they are as fast as loops (which they are).
import std/[tables]
import pkg/zero_functional
# A mapping from coordinates (x, y) to values.
var locations = {(1, 2): 1, (2, 1): 2, (-2, 5): 3}.toTable()
# Get the minimum X coordinate.
echo locations.keys --> map(it[0]).min()
# -2
There is kind of a way to compose iterators.
template `!`[T](it: iterable[T]): untyped =
(iterator(): T =
for i in it: yield i)
iterator double(it: iterator(): int): int =
for i in it():
yield 2 * i
for i in double(!double(!(1..3))):
echo i
This gives a segmentation fault but I can't fix it right now as I am on mobile and walking, it might work if you don't use iterable[T]
The segfault was unrelated to iterable[T] (as much as people rag on this feature), it was due to the hackiness of defining closure iterators inside the for statement iterator call. This works:
template `!`[T](it: iterable[T]): untyped =
(iterator(): T =
for i in it: yield i)
iterator double(it: iterator(): int): int =
for i in it():
yield 2 * i
let iter = !double(!double(!(1..3)))
for i in iter():
echo i
You can easily wrap more sugar around this. The only real "catch" here is that we are using closure iterators and not inlined iterators. But I'm not sure of any language where iterators are composable like this while also being inlined.