Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve let #3235

Closed
scharris opened this issue May 29, 2013 · 15 comments
Closed

Improve let #3235

scharris opened this issue May 29, 2013 · 15 comments
Assignees

Comments

@scharris
Copy link

Let is not as safe or convenient as it could be. The main issue is that with the current let behavior it is too easy to accidentally reassign variables in an outer scope instead of introducing new bindings.

  1. By forgetting a comma after "x = 1" below we accidentally overwrite
    an outer y instead of creating a local y:
y = ...
...
let x = 1       # <- forgot a comma here
    y = 1       # oops overwrote outer y
  x + y
end

The above is also different from

let
  x = 1
  y = 1
  x + y
end

which would reassign to an outer x as well, though it looks like only a stylistic difference.

It's unclear in both cases above where the let bindings end and where the body starts. An optional "in" before the body would help, see 4).

  1. Protecting a variable declaration inside the body of the let with "const" does not prevent reassignment to an existing variable outside the let as it usually does in other contexts.

For example, the below gives no error and reassigns the outer x instead of introducing a new variable x inside the let:

julia> function f()
     x = 1 # we don't want this one to be modified
     let
       const x = 50
       y = 1 + x
       x+y
     end
     x  
   end
julia> f()
50

Now changing "const" to "local" does create a new binding, but const either should also or give an error (It would be nice if it implied "local" - I like to declare new unchanging variables with const so variables to be mutated stand out).

  1. A let bound variable cannot depend on previous ones which makes lets much less useful than they could be. We often want to limit scope of all variables involved in a computation being built incrementally.

  2. Lets would be easier to read in many cases if an optional "in" were allowed between the bindings and the body. This would also make it clear the dividing line between the body and the bindings, so the examples in 1) would give errors instead of accidentally overwriting variables in outer scopes:

let x = 1       # <- forgot a comma here
    y = 1
  in x + y      # error! Missing comma in bindings detected.
end

I believe the optional "in" would also make many lets easier to read:

let pi = 3.14 pi*r^2 end

vs

let pi = 3.14 in pi*r^2 end
@quinnj
Copy link
Member

quinnj commented May 29, 2013

Take a look at #3205 for some add'l comments on let improvements.

@StefanKarpinski
Copy link
Member

Radical idea: how about getting rid of let altogether and rolling this into for since we want to make for loop variables behave like they're "autoletted" anyway? That's one less syntactic construct and it seems like you inevitably end up using a for loop to demonstrate why let is useful.

@JeffBezanson
Copy link
Member

Ok, ok.

(3) is totally intentional; let bindings are "simultaneous" in every language I know that has them. We could make it behave like let* in lisp, but then the problem is there's no way to get the old behavior back. You can implement sequenced bindings using

let x = 1
  let y = 2
    ...
  end
end

but if that's built in to let there's no way to get the current behavior.

@JeffBezanson
Copy link
Member

let is quite the whipping boy: #3205 #196 #1571

@StefanKarpinski
Copy link
Member

All the better to get rid of it ;-)

The current let is just syntax for immediately calling an anonymous function, which we would still be able to do.

@JeffBezanson
Copy link
Member

But that doesn't (won't) behave the same, for example with respect to return and break.

@JeffBezanson
Copy link
Member

Doesn't using for for this mean you have to make a 1-element container, as in for x = (y,) just to bind y to x? Not the end of the world I guess, but a bit awkward.

@scharris
Copy link
Author

Jeff, you don't have to wrap, somebody already handled this case, but on the other hand the for loop doesn't return the actual value as it stands now either:

julia> a = for x = 1, y=x+1; x+y end
julia> typeof(a)
Nothing

BTW: SML and Haskell do the sequential (dependent) binding, FWIW. SML's syntax looks very similar to the one proposed, except they add "val" in front of the value declarations. Haskell's is similar:

> let x = 1; y = x + 1 in x + y 
→ 3

I think if "const" implied "local" then let would be in better shape, because

let
  const x = 1
  const y = 2^x
  x + y
end

emulates the "dependent let" well enough, then you can still have the existing behavior when it's wanted. Or if we could have "val" as an alias for "const local" then even better. Probably people are already using "val" as variable name though.

@JeffBezanson
Copy link
Member

I agree with the idea of having const imply local, and with the optional in. Those would help.

You do have to wrap the right-hand side, because in for x = y if y is a container then x will iterate over it.

If people generally seem to want the sequential binding behavior, I'm ok with it.

@scharris
Copy link
Author

Ah - yes I see your point about having to wrap now.

The sequential bindings + "in" have gotten a lot of use in the ML's and Haskell, I think it's a tried and true approach and seems to be pretty well liked there.

@StefanKarpinski
Copy link
Member

It would be a breaking change, but we could make for x = y and for x in y behave differently in the following way: when the RHS of the for-= construct is not a range or container literal, it does let assignment instead of iteration. I'm not sure that's a good idea, but it's a possibility and it does jive with the established idiomatic usages of = versus in with for loops.

@JeffBezanson
Copy link
Member

I appreciate the creative thinking, but I think making

for i = 1:n

do something totally different from

R = 1:n
for i = R

is somewhere way beyond unacceptable.

@StefanKarpinski
Copy link
Member

Yeah, it's probably not a good idea.

@ghost ghost assigned JeffBezanson May 29, 2013
@JeffBezanson
Copy link
Member

Looking at the possibility of adding in, I'm starting to think it's a bad idea. We can't properly deal with all combinations, such as

let x = 1
    y = 2
  in
    x
end

without making in a completely reserved word.
You can currently use ; as a separator, as in let x=1; print(x); end

@JeffBezanson
Copy link
Member

Other example:

let x = 1
    y in z

...
end

if we wanted that to be a let syntax error, it would preclude using in as an infix operator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants