Closed arthurhjorth closed 8 years ago
I want to note that none of these are particularly challenging to implement if the rules are set up rather rigorously. The final is just a matter of bracket counting.
Middle is easiest to implement, since it's already implemented! We just convert the code blocks to strings and... done!
Another possibility would be local variable injection:
let foo count turtles
ls:ask child [ set global-var foo ]
I think I might prefer ls:of-myself
. It has fairly clean semantics, though I was hoping to avoid having to parse and transform the code block itself.
I like the middle one - that is, (ls:ask child [set global-var ?] count turtles)
My reasoning against [3] is that myself is notoriously a challenge in NetLogo, and it requires an additional construction of the present model as an agent. It would be good to encourage that way of thinking, but this syntactic moment doesn't feel like the place to demand that.
Along a parallel but different line of thinking, [1] seems to pose an even worse problem to me because it not only involves a myself-like construction but it ALSO does so while invoking parent-child in the syntactic place of myself-self.
So, I think that my order of preference would be [2], [3], [1].
Question to @qiemem : Does the local variable injection come "for free"? It seems like a good option to offer, if the cost is low.
Now that I've thought about it more, I actually dislike the second one. The way I came to this conclusion was by thinking about more intermediate netlogo code with larger blocks.
(ls:ask child [
crt ? [
fd ?
rt ?
set shape ?
]
] get-val1 get-val2 get-val3 get-val4)
Then if you wanted to insert something in the middle, you have to line up the args with the question marks, and may get confused. The bug will also be really subtle if you do something out of order, and you also have to do ? matching with the number of arguments. These are all things that get resolved by doing it inline:
(ls:ask child [
crt [get-val1] ls:of-myself [
fd [get-val2] ls:of-myself
rt [get-val3] ls:of-myself
set shape [get-val4] ls:of-myself
]
]
well... (this is to Frank about his last post) --> here is where I would want the local variable injection idea that @qiemem mentioned. So
let v1 get-val1 let v2 get-val2 let v3 get-val3 let v4 get-val4 (ls:ask child [ crt v2 [ fd v2 rt v3 set shape v4 ] ])
Ironically, you have a bug where you accidentally used v2 twice. That's the kind of subtle bug that is annoying to track down, especially with the limited debugging available when executing remote code.
blah. well, i wouldn't have used such bad variable names except that i had to follow your code example :)
/\/\/\ Ha!
I can see how it's a neat idea, but I really don't like variable injection. It totally changes how lets
work, and I don't want for LevelSpace. At least the ?
-semantics are well-established in NetLogo already.
? @arthurhjorth ?
do you disagree that...
let share-ideas .1 * ideas
ask neighbors [ set ideas ideas + share-ideas ]
could be reasonably normative netlogo? i would consider it superior for code early in an intro course, compared to
ask neighbors [ set ideas ideas + [ .1 * ideas ] of myself ]
or, maybe i don't understand the claim about totally changing how lets work
Maybe I am not either - maybe I had a wrong intuition about it. I think I felt that it would be a frivolous use of let
. But maybe not, because I can't see how what you're suggesting isn't the best way to do it.
One point about the let
stuff: The semantics are guaranteed to be a little weird and inconsistent.
I'm still not sure what the right answer is. The problems with arguments are quite as bad as Frank made out since he didn't use ?1
arguments. So his example is actually:
(ls:ask child [
crt ?1 [
fd ?2
rt ?3
set shape ?4
]
] get-val1 get-val2 get-val3 get-val4)
Now if you want to inject something in the middle, you can just do it. If you want to name your arguments, you can just use lets in the code block:
(ls:ask child [
let num-turtles ?1
let dist ?2
let angle ?3
let appearance ?4
crt num-turtles [
fd dist
rt angle
set shape appearance
]
] get-val1 get-val2 get-val3 get-val4)
This is pretty much the same verbosity as with let-injecting:
let num-turtles get-val1
let dist get-val2
let angle get-val3
let appearance get-val4
ls:ask child [
crt num-turtles [
fd dist
rt angle
set shape appearance
]
]
Again, I'm still not sure what's right, but I do like the explicitness of arguments.
Alright, I have a new proposal. I dislike ls:of-myself
because myself
is confusing, and you can get into some really weird situations with the proposed implementation of ls:of-myself
. (I'll post examples if people are curious)
So, what about ls:of-parent
? It always goes to the child's model's parent (myself
does not necessarily do this), so doesn't have any of the weird ambiguity of myself
. Here's the example we've been using. It's identical to myself
in this case:
ls:ask child [
crt [ get-val1 ] ls:of-parent [
fd [ get-val1 ] ls:of-parent
rt [ get-val1 ] ls:of-parent
set shape [ get-val1 ] ls:of-parent
]
]
of
.?
. Again, I'm fine with ?
, but I recognize others are not.I dislike the proposed AST-preprocessing implementation of the ls:of-myself
. The fact that the "myself"-block has to be prior-to the rest of the code block creates some super weird situations (again, I can provide examples). So, instead, I propose adding ls:of-parent
to LevelSpace as a normal primitive. This goes against a long held opinion of mine, but I think I was wrong. @arthurhjorth is probably going to ask "what about the class loader?" at this point. That's actually pretty easy to get around I believe with a touch of reflection and a callback.
We can inject LevelSpace into models that don't use it normally so that they can use this procedure. Regarding whether or not this is possible, I quote @mrerrormessage from gitter:
I don't know the exact procedure, but it can probably fool around with the extension manager if it can't, we should consider making it possible to do that
Hmm, just realized a tricky part of the ls:of-parent
. In the simplest implementation, it loses context, so you can't reference local variables. You can only use reporters/globals.
However, I think we can get around this without too much trouble. The parent can give the child its context to use when the child runs ls:of-parent
. So, pseudo code for ls:ask
:
function askChild(child, command, context) {
oldContext = child.getParentContext() // There may be a parent context already in deeply nested things
child.setParentContext(context)
child.runCommand(command)
child.setParentContext(oldContext)
}
and ls:of-parent
:
function ofParent(reporter) {
parent.runCommand(reporter, getParentContext())
}
I guess context
is an optional argument of runCommand
. Obviously, this is very pseudo-y code and doesn't deal with the reflection shenanigans you have to do because classloader.
If we can run a reporter in the parent, could we run commands in the parent too?
Um, I guess. Anyway, opinions on the reporter syntax?
I'm not sure I understand this part: "Hmm, just realized a tricky part of the ls:of-parent. In the simplest implementation, it loses context, so you can't reference local variables. You can only use reporters/globals."
Can you give an example of what you mean? Can we not reference local variables in the child model that runs ls:of-parent
, or can we not reference local variables in the parent? The former seems like a problem, but the latter seems like the way it should be. At least the way I'm thinking about it right now.
I figured out a pretty obvious way to get the benefits let-injection without possible collisions. If we add symbol-support to the NetLogo compiler (which I just did...), we can add ls:let
, which just creates bindings to inject into ls:ask
and ls:of
. So:
ls:let num-turtles get-val1
ls:let dist get-val2
ls:let angle get-val3
ls:let appearance get-val4
ls:ask child [
crt num-turtles [
fd dist
rt angle
set shape appearance
]
]
I've just implemented this and it seems to work pretty well. ls:let
actually stores the bindings in the Context
, just like let
, so it's lexically scoped, just like let
. Thus, your ls:let
bindings don't leak. They inject by just sticking more arguments onto the task. Super simple. I've implemented it for ls:ask
so far.
I like it
I really, really like it too! It's (arguably) slightly more verbose than the ls:of-myself alternative, but it reads so much better! Awesome job! A few questions:
Do you have to have declared the num-turtles
or angle
, etc. first? or
can you give it the name of a new variable that has never been initialized
or mentioned before?
Maybe you answered this in CSy terms when you talked about leaking (which I didn't quite understand), but what if people name their lets the same as a variable in the other model?
Arthur Hjorth PhD Student, Learning Sciences Center for Connected Learning http://ccl.northwestern.edu/ Northwestern University
e: arthur.hjorth@u.northwestern.edu s: arthurhjorth m: +1 (773) 943-0013
On Sun, Oct 11, 2015 at 6:35 AM Seth Tisue notifications@github.com wrote:
I like it
— Reply to this email directly or view it on GitHub https://github.com/NetLogo/LevelSpace/issues/56#issuecomment-147184702.
Bryan - This is awesome What variable types are allowed? Did you just also enable agents to be local ls:ask variables? That is, does this work?
repeat 10 [
let /*childmodel*/ 0
ls:let *aturtle* one-of turtles
ls:ask /*childmodel*/ [
crt 10 [
set shape [shape] of *aturtle*
]
]
ls:ask /*childmodel*/ [
crt 5 [
setxy [xcor] of *aturtle* [ycor] of *aturtle*
]
]
]
(i do the 2 ls:asks in case that's related to arthur's question about the scope the ls:let has the same scope (in terms of the surrounding netlogo model) as any other let, if i undertand you right.)
-c
On 10/11/15 12:33 AM, Bryan Head wrote:
I figured out a pretty obvious way to get the benefits let-injection without possible collisions. If we add symbol-support to the NetLogo compiler (which I just did...), we can add |ls:let|, which just creates bindings to inject into |ls:ask| and |ls:of|. So:
|ls:let num-turtles get-val1 ls:let dist get-val2 ls:let angle get-val3 ls:let appearance get-val4 ls:ask child [ crt num-turtles [ fd dist rt angle set shape appearance ] |
I've just implemented this and it seems to work pretty well. |ls:let| actually stores the bindings in the |Context|, just like |let|, so it's lexically scoped, just like |let|. Thus, your |ls:let| bindings don't leak. They inject by just sticking more arguments onto the task. Super simple. I've implemented it for |ls:ask| so far.
— Reply to this email directly or view it on GitHub https://github.com/NetLogo/LevelSpace/issues/56#issuecomment-147160060.
Scratch my last email - I didn't read carefully. Bryan, maybe you could simulate the binding that happens from this (i.e., the task that gets sent to the child model) If I understand right, Arthur's concern about collision between ls:let vars and globals in the child doesn't happen because the ls:let variables (num-turtles etc) are replaced by positional arguments before they get to the child model's context for interpretation. Is that right? --c
On 10/11/15 12:33 AM, Bryan Head wrote:
I figured out a pretty obvious way to get the benefits let-injection without possible collisions. If we add symbol-support to the NetLogo compiler (which I just did...), we can add |ls:let|, which just creates bindings to inject into |ls:ask| and |ls:of|. So:
|ls:let num-turtles get-val1 ls:let dist get-val2 ls:let angle get-val3 ls:let appearance get-val4 ls:ask child [ crt num-turtles [ fd dist rt angle set shape appearance ] |
I've just implemented this and it seems to work pretty well. |ls:let| actually stores the bindings in the |Context|, just like |let|, so it's lexically scoped, just like |let|. Thus, your |ls:let| bindings don't leak. They inject by just sticking more arguments onto the task. Super simple. I've implemented it for |ls:ask| so far.
— Reply to this email directly or view it on GitHub https://github.com/NetLogo/LevelSpace/issues/56#issuecomment-147160060.
Do you have to have declared the
num-turtles
orangle
, etc. first? or can you give it the name of a new variable that has never been initialized or mentioned before?
It must be a new variable that's never been mentioned before in that scope.
So this breaks:
ls:let foo 5
ls:let foo 6
and this breaks:
ls:let foo 5
ask turtles [
ls:let foo 6
]
but this works fine since the first foo
's scope ends before the second:
ask turtles [
ls:let foo 5
]
ls:let foo 6
This is exactly like let
because ls:let
pretty much is let
under the hood.
Maybe you answered this in CSy terms when you talked about leaking (which I didn't quite understand), but what if people name their lets the same as a variable in the other model?
It will error with, for instance: There is already a global variable called FOO
.
However, Corey's suggestion:
collision between ls:let vars and globals in the child doesn't happen because the ls:let variables (num-turtles etc) are replaced by positional arguments before they get to the child model's context for interpretation.
would work. Right now, this:
ls:let foo 5
ls:ask 0 [ crt foo ]
gets translated into:
(ls:ask 0 [
let foo ?1
crt foo
] 5)
but it would be easy enough to just swap out foo
s for ?1
s instead (though it might make error messages harder to read).
The advantage of ls:let
over let
injection is that avoids many unintentional collisions. However, I think we should still error on collisions. I can definitely see people sending a variable foo
to a model with a global foo
and trying to access both in the same block without thinking about it (remember, blocks can be arbitrarily long and complex). With Corey's suggestion, the ls:let
foo
would mask the global one. If we don't error, they won't necessarily realize anything is wrong. Cool idea though Corey, I hadn't thought of that.
Should we still allow the argument syntax? e.g. (ls:ask 0 [ crt ? ] 50)
I realized the prefix with let foo ?1
s to inject lets doesn't work with reporters. So I'm just going to substitute foo
tokens with ?1
tokens for now. This means that ls:let
locals will actually shadow the child model variables with the same name. I don't think this is good for the long term, but it's good enough for now.
We now do let foo ?1
prefixing as of 0ea7978021574609deacd665a7f7d90a2bd51e56
We need to make some decisions about LevelSpace CodeBlock syntax. Basically the question is how to resolve/evaluate reporters that are ambiguous. Let's make it concrete and say that we want to set a global variable in a child model to a list of xcors from its parent model. The way we would do that now is:
(ls:ask child "set global-var ?" count turtles)
In a
CodeBlock
I'm less sure how that would work. I talked with @frankduncan about this, and we came up with three different approaches:ls:ask child [set global-var (ls:eval-in-parent count turtles)]
(ls:ask child [set global-var ?] count turtles)
ls:ask child [set global-var [count turtles] ls:of-myself]
My preference is the last because it's closest to current NetLogo. My 2nd preference is the middle one, because it works like current tasks do, which makes it NetLogo-like too. My least favorite is the first one, just because the
eval-in-parent
reporter seems not very NetLogo-like. But it might be easier to implement.What do you guys think?