Implementing map & min that takes the tables.keys iterator as argument in Nim

Viewed 215

I would like to define overloads of map and min/max (as originally defined in sequtils) that works for tables.keys. Specifically, I want to be able to write something like the following:

import sequtils, sugar, tables

# A mapping from coordinates (x, y) to values.
var locations = initTable[(int, int), int]()
# Put in some random values.
locations[(1, 2)] = 1
locations[(2, 1)] = 2
locations[(-2, 5)] = 3

# Get the minimum X coordinate.
let minX = locations.keys.map(xy => xy[0]).min
echo minX

Now this fails with:

/usercode/in.nim(12, 24) Error: type mismatch: got <iterable[lent (int, int)], proc (xy: GenericParam): untyped>
but expected one of:
proc map[T, S](s: openArray[T]; op: proc (x: T): S {.closure.}): seq[S]
  first type mismatch at position: 1
  required type for s: openArray[T]
  but expression 'keys(locations)' is of type: iterable[lent (int, int)]

expression: map(keys(locations), proc (xy: auto): auto = xy[0])

Below are my three attempts at writing a map that works (code on Nim playground: https://play.nim-lang.org/#ix=3Heq). Attempts 1 & 2 failed and attempt 3 succeeded. Similarly, I implemented min using both attempt 1 & attempt 2, and attempt 1 failed while attempt 2 succeeded.

However, I'm confused as to why the previous attempts fail, and what the best practice is:

  • Why does attempt 1 fail when the actual return type of the iterators is iterable[T]?
  • Why does attempt 2 fail for tables.keys? Is tables.keys implemented differently?
  • Is attempt 2 the canonical way of taking iterators / iterables as function arguments? Are there alternatives to this?

Attempt 1: Function that takes an iterable[T].

Since the Nim manual seems to imply that the result type of calling an iterator is iterable[T], I tried defining map for iterable[T] like this:

iterator map[A, B](iter: iterable[A], fn: A -> B): B =
  for x in iter:
    yield fn(x)

But it failed with a pretty long and confusing message:

/usercode/in.nim(16, 24) template/generic instantiation of `map` from here
/usercode/in.nim(11, 12) Error: type mismatch: got <iterable[(int, int)]>
but expected one of:
iterator items(a: cstring): char
  first type mismatch at position: 1
  required type for a: cstring
  but expression 'iter' is of type: iterable[(int, int)]
... (more output like this)

From my understanding it seems to say that items is not defined for iterable[T], which seems weird to me because I think items is exactly what's need for an object to be iterable?


Attempt 2: Function that returns an iterator.

I basically copied the implementation in def-/nim-itertools and defined a map function that takes an iterator and returns a new closure iterator:

type Iterable[T] = (iterator: T)

func map[A, B](iter: Iterable[A], fn: A -> B): iterator: B =
  (iterator: B =
    for x in iter():
      yield fn(x))

but this failed with:

/usercode/in.nim(25, 24) Error: type mismatch: got <iterable[lent (int, int)], proc (xy: GenericParam): untyped>
but expected one of:
func map[A, B](iter: Iterable[A]; fn: A -> B): B
  first type mismatch at position: 1
  required type for iter: Iterable[map.A]
  but expression 'keys(locations)' is of type: iterable[lent (int, int)]
proc map[T, S](s: openArray[T]; op: proc (x: T): S {.closure.}): seq[S]
  first type mismatch at position: 1
  required type for s: openArray[T]
  but expression 'keys(locations)' is of type: iterable[lent (int, int)]

expression: map(keys(locations), proc (xy: auto): auto = xy[0])

which hints that maybe tables.keys doesn't return an iterator?


Attempt 3: Rewrite keys using attempt 2.

This replaces tables.keys using a custom myKeys that's implemented in a similar fashion to the version of map in attempt 2. Combined with map in attempt 2, this works:

func myKeys[K, V](table: Table[K, V]): iterator: K =
  (iterator: K =
    for x in table.keys:
      yield x)
0 Answers
Related