Open shriram opened 8 years ago
Also rec + shadow.
The operational reason is because the grammar is specified to reuse binding forms whenever possible:
let-expr: toplevel-binding EQUALS binop-expr
letrec-expr: LETREC letrec-binding* let-expr COLON block end
letrec-binding: let-expr COMMA
multi-let-expr: LET let-binding-elt* let-binding COLON block end
let-binding-elt: let-binding COMMA
let-binding: let-expr | var-expr
var-expr: VAR toplevel-binding EQUALS binop-expr
rec-expr: REC toplevel-binding EQUALS binop-expr
toplevel-binding: [SHADOW] NAME [COLONCOLON ann]
It seemed more consistent at the time. Refactoring that part of the grammar will be a bit fiddly, but shouldn't cause too many problems.
There's a better reason – let has multiple arms:
let x = 5 ,shadow x = 10 in x end
It would be odd to shadow on a per-let basis:
let x = 5, y = 20 in
shadow let x = 10, y = 5 in
x + y
end
end
Since the shadowing is per-name, not per-binding-construct.
Oh right, that reason :) Good point.
Great explanation. Can the parser help the poor user who doesn't see this issue?
Mmm, not sure whether Joe's point actually applies here. Both of Shriram's original complaints leave the let
all the way on the left; it's just whether the shadow
and var
"commute". So
let var x = 5, shadow var x = 20 in x end
versus
let var x = 5, var shadow x = 20 in x end
I agree the former is more readable, and I don't think it breaks multi-arm lets.
Mmm I see. Yup I misunderstood the issue.
shadow
and var
are both kinds of binding modifier. Interestingly, we allow shadow
anywhere a binding appears, but allow var
only in special positions.
Should those be part of the same binding spec?
rec
is a little different, since it affects scoping blocks, and doesn't necessarily apply to e.g. function arguments, which both shadow
and var
could naturally apply to.
Agreed: let
and rec
define sequences of bindings; var
and shadow
help specify what the kind of binding is.
Can var
really be used for function arguments? Seems like it shouldn't...
var
on function parameter bindings is actually something I asked Joe about last night.
Is there a good reason to do that? I don't know [*]. BUT, it feels like orthogonality suggests that there is a single notion of “binding spec”, and if that notion includes var
, then var
should be allowed here.
[*] Well, I know a bit.
Argument in favor: Sometimes it is useful to mutate something instead of creating an extra scope to shadow it. So I can just about see some value to this. (Imagine a case where you get an argument, and you want to cleanse it, and afterwards just modify it so that the rest of the function sees only the cleansed version and doesn't accidentally obtain the pre-cleansed version.)
Argument against: This is really about conventions dictated by other languages. A var
parameter in many languages means you're passing by-reference, so one would expect
fun f(var x): x := 3 end
var y = 10
f(y)
check:
y is 3
end
to pass. But in Pyret, y is 10
is the version that would pass, because we are most assuredly not going to introduce variable aliasing (shudder!). So there's this weird thing about how it might be perceived by people with experience from the C family. (But there's fewer and fewer of those, especially in earlier CS courses.)
For your initial argument in favor -- we made an explicit design/pedagogy choice for Pyret that if you wanted to cleanse a parameter, you darned well better say shadow theParam = cleanse(theParam)
at the top of your function, precisely to avoid the perception that you could modify function arguments.
How could you even call a function whose argument was specified as var
, if you passed in a particular value? You couldn't, and that would complicate the type system (we'd have types T
, Ref<T>
, and Var<T>
, and the latter two are not quite the same...), the compilation strategy of functions (because we wouldn't know whether to dereference the variable before sending it along to the function), etc. etc. and seems totally not worth it to me. In your argument against, I genuinely don't know how to compile that code.
Whether an identifier definition is a simple binding or a variable binding just might not be orthogonal from where we permit such definitions to appear. I'm ok with that.
I think Shriram is proposing that
fun f(var x):
...
end
desugars to
fun f(x1):
var x = x1
...
end
So a var
parameter spec is not visible to the caller at all.
A thought I had this afternoon: what if we changed the keyword? var
is very problematic for the reason I stated in my most recent post. But if we used something like mut
instead, that problem goes away. It does mean we no longer get code that looks like this:
fun f(x):
var y = 10
…
which has a nice JavaScripty flavor…but:
(1) We're not really looking to emulate JavaScript.
(2) Arguably, this actually causes problems. Students who do have JavaScript experience write the above, find that it works, and have now inadvertently made all their variables mutable, which is the opposite of what we want. (We see students early in the semester writing var
everywhere until the TAs tell them not to; it's not clear what causes this, but this is one possible explanation.)
In addition, using var
arguably creates some problems for our locution with algebra teachers. Non-mutable variables are what they are used to calling “variables”. So our var
actually turns what they call a variable into something they don't even have a vocabulary for. Surely this will be problematic at some point, @schanzer?
I'd say it causes problems the moment I try to sell this to math teachers. I don't know if they're a priority, you can be sure this hurts our "we take math seriously" salespitch.
This is definitely a "when vocabulary worlds collide" moment that hits an important point on the algebra-to-programming spectrum. I see a trichotomy:
(Also, if this discussion is continued, we should probably move it to an issue like "var
keyword considered harmful", rather than having it live here.)
I agree with both your posts, @jpolitz. How do you feel about changing the keyword? Anyone object to doing so (without getting into what that other keyword might be)? Joe and @blerner, I'd like to hear from you two in particular. Then we can close out this part of this thread. (I feel we can't really move on the var
can be used anywhere issue without addressing what happens if var
shows up in a function parameter.)
I'm not too averse to changing the keyword, though I don't see many alternatives (and don't much like mut
). Of Joe's three options above, the first one is likely the cleanest, but also the most primly pedantic of the three. I sympathize with Emanuel's point about making the terminology rough on math teachers, so I'll argue from the programming side that it would be rather self-foot-shooting to say, "oh no, our language doesn't have variables -- we have identifiers or mutables," which lets teachers give a non-thinking brush-off (of the same kind of non-thinking reasoning that says, conversely, "oh good they have for loops") Worse, when students move on to another language they'll say "thank goodness I don't need to be that nitpicky anymore!" and discard that pedantry with relief.
(On the other hand, Bob Harper would be thrilled that we have assignables instead of variables. But using "assignable" is even worse, in a math setting, where assignment already has a meaning...)
I still don't like having var
appear as a function parameter, no matter what it's called. I'd rather not have magic sugar there, and require the explicit shadow var
declaration.
It's not clear to me why the right order is
let var shadow
instead oflet shadow var
.At the very least, the error message for
let shadow var
should suggest the opposite.