First of all, I realize that this might go somewhat against the grain of Nim. And also it's not how I normally write code in Nim. I will start by explaining very briefly what I am doing and what's my motivation but feel free to skip to the last code example and the actual question following it. I'm currently experimenting with closures. I know that I can capture the reference to an object or an attributes of an object like so:
type
Book = ref object
title: string
var moby = Book(title: "Moby Dick")
let getTitle = proc(): string = moby.title
echo getTitle()
What I find interesting is that getTitle does not directly depend on the Book type. And also when I change the title of the book itself (i.e. moby) the getTitle function will return the correct (current) title. I can likewise implement a setter that can manipulate an attribute without an explicit reference to the type:
let setTitle = proc(newTitle: string) = moby.title = newTitle
Additionally, I can define these anonymous functions for (single, concrete) objects of different types (say Movie and Book) and then have a homogeneous access, meaning I can put multiple getTitle closures into a sequence seq[proc(): string], even if they act on different types of objects.
I'm currently using this approach in an experimental tweening library. I think tweening really is a cross-cutting concern and I want to keep the coupling to the objects being tweened very minimal.
Sidenote: This looks somewhat similar to implementing getTitle[T](obj: T): string but I can't have a seq[T]. I also thought about tagged unions but as far as I know these can't be easily extended.
Now on to my question. I tried this with a list of books and a for loop:
type
Book = ref object
title: string
var
books = @[
Book(title: "War and Peace"),
Book(title: "Of Mice and Men"),
Book(title: "Moby Dick")
]
titleReaders: seq[proc(): string]
for i in 0..2:
let w = proc(): string = books[i].title
echo "Getter called inside loop: ", w()
titleReaders.add(w)
for tr in titleReaders:
echo "Getter called outside loop: ", tr()
Link: https://play.nim-lang.org/#ix=3zRL
Here the closure inside the for loop also captures the loop variable i which equals 2 once the loop has finished. So all my title getters return "Moby Dick". (And it's also the same with a for b in books type of loop.)
Is there a way to capture "the book itself" inside the loop, basically resolving one level of reference? I thought maybe with addr or [] but I couldn't get it to work. (I guess I could also write an alternative loop as a macro that expands into separate statements but I would rather stick to built-in loop constructs.)
In your example, that would be something like
for i in 0..2:
let w = (proc(i: int): auto = proc(): string = books[i].title)(i)
titleReaders.add(w)
The initial version was expected. But then I tried example below, and it was not expected. Why let j = i didn't captured the current value of i?
var fns: seq[proc(): void]
for i in 0..1:
let j = i
fns.add proc = echo j
for fn in fns: fn()
This small change (addition of var j) to your original posting works.
OK But why does not work next code? (there is no autocapture and local variable p made explicitly) It is bad from viewpoint of the language consistency.
var fns: seq[proc: void]
for i in 0..10:
let w = proc(): auto =
let p = i
echo p
fns.add w
for fn in fns: fn()
var fns: seq[proc()]
for i in 0..10:
closureScope:
let p = i
let w = proc() =
echo p
fns.add w
for fn in fns: fn()
does what you asked for, I think.
variable should be captured by value and it did not so
Yes, this is what confused me too. let a: int = b means that it's immutable and copied by value.
We changed it for better performance and better JavaScript interop.
Can't comment about the performance. But as far as I know in JavaScript this behavior (lack of scope for if/for/etc statements) considered as unfortunate design mistake that's impossible to fix due to backward compatibility.
more over i wonder if its properly covered in the Nim manuals.
Its documented Agreed though it's not the most intuitive thing and I had trouble learning it, as well.
About JavaScript, as far as I know in JS this behavior (lack of scope for if/for/etc statements) considered more like an unfortunate design mistake, that would be nice to fix, but unfortunately it's impossible to fix due to backward compatibility.
Yes, I know, but please consider that we map Nim's closures to JavaScript's closures one-to-one so there is no mismatch or additional overhead. And yes, maybe there is a better design for the problem ("only map it directly to JS if not within a loop") but closures are a very expensive feature. I wrote the transformation 3 times already, it's a complex algorithm and lots of cases have to be considered...