There's any way I could implement a generic proc that can receive a proc with {.nimcall.} or {.closure.} calling convention? I now that if I specify the type as {.closure.} it can accept {.nimcall.} but this way is way more inneficient and is stored with two words. What I want is some way to have a struct like this:
type FilterIterator[I,F,A] = object
inner: I
predicate: F
Where F is the type of a proc that if passed a {.nimcall.} will be stored efficiently. I don't know how to write the function to construct this object.
Sorry for my english.
You probably want to hve a look a Timothee Cour's attempt at reproducing D ranges here: https://github.com/nim-lang/Nim/issues/9422
Otherwise here is a skeleton:
type
FilterProc[I] = proc(x:I): bool {.nimcall.}
FilterIterator[I] = object
# I should probably be a pointer + len pair like https://github.com/status-im/nim-ranges/blob/master/ranges/memranges.nim
# as you can't capture openarrays
inner: seq[I]
predicate: FilterProc[I]
func initFilter[I](
x: seq[I],
cond: proc(x:I): bool {.nimcall.}
): FilterIterator[I] =
result.inner = x
result.predicate = cond
func filterGT0(x: int): bool =
if x > 0: true
else: false
let f = initFilter(@[1, 2, 3], filterGT0)
I did it this way, but correct me if I'm wrong but filter cannot accept a closure implemented like this. What I wanted to do is to support closures but to store nimcall efficitienly (not as closures).
I thought that I could receive the proc type as a generic parameter (this way the proc could be {.nimcall.} or {.closure.}) and have it be stored on the object. But I found no way to do this.
Thanks for the link. I'll take a look.
Just changing from {.nimcall.} to {.closure.} makes it two times slower. Here's my code:
import sugar
type
Enumerator*[A] = concept i, var x
x.tryAdvance(var A) is bool
iterator items*[A](iter: Enumerator[A]): A {.inline.} =
var it {.noinit.} = iter
var elem {.noinit.}: A
while it.tryAdvance(elem):
yield elem
proc fold*[A,B; I: Enumerator[A]](iter: I, init: B, reducer: proc (acc: B, value: A): B {.nimcall.}): B {.noinit, inline.} =
result = init
for elem in iter:
result = reducer(result, elem)
template foldIt*[A](iter: Enumerator[A], init: untyped, code: untyped): untyped =
block:
var acc {.inject.} = init
for it {.inject.} in iter:
acc = code
acc
type
EmptyEnumerator*[A] = object {.requiresInit.}
FilterEnumerator*[I,A] = object {.requiresInit.}
source: I
predicate: proc (value: A): bool {.nimcall.}
MapEnumerator*[I,A,B] = object {.requiresInit.}
source: I
mapper: proc (value: A): B {.nimcall.}
proc tryAdvance*[A](iter: var EmptyEnumerator[A], output: var A): bool {.noinit, inline.} = false
proc tryAdvance*[A; I: Enumerator[A]](iter: var FilterEnumerator[I,A], output: var A): bool {.noinit, inline.} =
while iter.source.tryAdvance(output):
if iter.predicate(output):
return true
return false
proc tryAdvance*[A,B; I: Enumerator[A]](iter: var MapEnumerator[I,A,B], output: var B): bool {.noinit, inline.} =
var tmp: A
result = iter.source.tryAdvance(tmp)
if result:
output = iter.mapper(tmp)
proc filter*[A; I: Enumerator[A]](iter: I, predicate : proc (value: A): bool {.nimcall.}): FilterEnumerator[I,A] {.noinit, inline.} =
return FilterEnumerator[I,A](source: iter, predicate: predicate)
proc map*[A,B; I: Enumerator[A]](iter: I, mapper: proc (value: A): B {.nimcall.}): MapEnumerator[I,A,B] {.noinit, inline.} =
return MapEnumerator[I,A,B](source: iter, mapper: mapper)
type
Range*[A] = object {.requiresInit.}
start: A
limit: A
step: Positive
proc iter*[A: Ordinal](source: HSlice[A,A]): Range[A] {.noinit, inline.} =
return Range[A](start: source.a, limit: source.b, step: 1)
proc irange*[A: Ordinal](start: A, limit: A, step: Positive = 1): Range[A] {.noinit, inline.} =
return Range[A](start: start, limit: limit, step: step)
proc xrange*[A: Ordinal](start: A, limit: A, step: Positive = 1): Range[A] {.noinit, inline.} =
return Range[A](start: start, limit: pred(limit), step: step)
proc tryAdvance*[A: Ordinal](iter: var Range[A], output: var A): bool {.noinit, inline.} =
output = iter.start
if output <= iter.limit:
inc(iter.start, iter.step)
return true
return false
when isMainModule:
import times
template timeit(code: untyped): untyped =
block:
let start = cpuTime()
let result = code
let delta = cpuTime() - start
echo "Result: " & $result
echo "Elapsed time: " & $delta & "s"
proc main() =
let n = 1_000_000_000
timeit:
var sum = 0
for x in 0..n:
if x mod 3 == 0:
sum += x * 2
sum
timeit:
irange(0, n).filter(x => x mod 3 == 0).map(x => x * 2).foldIt(0, acc + it)
main()
The output with {.nimcall.}:
Result: 333333333666666666
Elapsed time: 1.291738s
Result: 333333333666666666
Elapsed time: 1.238424s
The output with {.closure.}:
Result: 333333333666666666
Elapsed time: 1.266814s
Result: 333333333666666666
Elapsed time: 2.506261s
With {.nimcall.} it's a little faster than the for loop version.