brownplt / pyret-lang

The Pyret language.
Other
1.07k stars 110 forks source link

let var shadow vs. let shadow var #666

Open shriram opened 8 years ago

shriram commented 8 years ago

It's not clear to me why the right order is let var shadow instead of let shadow var.

At the very least, the error message for let shadow var should suggest the opposite.

sorawee commented 8 years ago

Also rec + shadow.

blerner commented 8 years ago

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.

jpolitz commented 8 years ago

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.

blerner commented 8 years ago

Oh right, that reason :) Good point.

shriram commented 8 years ago

Great explanation. Can the parser help the poor user who doesn't see this issue?

blerner commented 8 years ago

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.

jpolitz commented 8 years ago

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.

blerner commented 8 years ago

Agreed: let and rec define sequences of bindings; var and shadow help specify what the kind of binding is.

blerner commented 8 years ago

Can var really be used for function arguments? Seems like it shouldn't...

shriram commented 8 years ago

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.)

blerner commented 8 years ago

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.

jpolitz commented 8 years ago

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.

shriram commented 8 years ago

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?

schanzer commented 8 years ago

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.

jpolitz commented 8 years ago

This is definitely a "when vocabulary worlds collide" moment that hits an important point on the algebra-to-programming spectrum. I see a trichotomy:

jpolitz commented 8 years ago

(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.)

shriram commented 8 years ago

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.)

blerner commented 8 years ago

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...)

blerner commented 8 years ago

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.