Yes, but a different meaning outside them.
How so?
i = 0
println(i)
i = 1
println(i)
# rest of the unrolled loop goes here
julia> i=0:9; println(i)
0:9
julia> for i=0:9; print(i) end
0123456789
Hmm, so it is. I happen to prefer in
as the operator in for
loops, itās a bit cleaner.
But this is not a homonym, =
means assignment in both contexts. A word which is both an adjective and a noun is not a homonym of two words, consider āthis is a red bookā and āred is my favorite colorā, obviously not homonyms here.
This case is similar: a for
loop means āassign to this variable repeatedlyā. It would be a poor for loop which couldnāt execute more than once.
Extending your argument to my version, =
is not a homonym because it means equality in all contexts. let
and set
merely force that relationship by associating the identifier on the left with the value on the right.
Or I could rebut your argument, but I wonāt bother.
At the end of the day what matters is whether your code is nice to write, nice to read, and nice to run, not whether we can find obscure names to describe what itās doing
Thatās exactly why I only use in
for iterations
Then you just shift the problem around, since in
becomes the homonym:
julia> for i in 0:9; print(i) end # iterate 0:9 and repeatedly assign i
0123456789
julia> i in 0:9 # test if i is an element of 0:9
ERROR: UndefVarError: `i` not defined
Weāre forced to face the fact that homonyms are not actually a problem, as long as theyāre reasonably coherent and reasonably obvious.
What is āreasonableā is subjective of course, but itās not entirely arbitrary; Julia tries to maximize coherence with math syntax and with other popular programming languages, whose syntax has evolved into a pretty decent form as proven by their survival, and with a bias toward aligning with math syntax (see Edelman talk). Iād just prefer if Julia pushed a little closer to math syntax, since thatās evolved for human-to-human communication.
Most of the time Iām not using continue
or break
so I like to use more restrictive structures like foreach
or map
which donāt have this problem.
I prefer foreach
and map
too, but for terseness and the function barrier (great for iterating over type-heterogenous collections, but be careful of #15276). Do you really use them just to avoid the homonyms?
I do actually find the code harder to read as let
, for
and set
are of the same length, but I do find it more consistent with respect to set a += i
VS nonequivalent way of doing that with :=
.
An alternative I would like to have have is that every assignment by default would be a let
binding and if one needs to reassign variable it shall be declared with var
:
var s = 0
for i in 1:10
s += i
end
This would solve the issue of accidental variable reasignment which bites me occasionally while leaving the code clean.
Yep, and ā
| ā
for inclusion a la if x ā [1,3,5]
. In for
and if
blocks, =
| in
| ā
are synonymsā , three different ways of saying the same thing. So I take the opportunity to use them distinctly: =
for declaration/assignment (which makes =
in assignment expressions a homonym, QED), in
for iteration, ā
for inclusion in a collection.
Itās purely a style thing, but these are three distinct semantics, and this usage makes that difference stand out on the screen. Having been until recently a professional Lua programmer, where for x =
and for x in
are different constructs (numeric and generic), itās natural for me to write iteration in the latter. I do sometimes use for x = 1:9
specifically for ranges, for the same reason (Lua habit), but Iāve been changing those to in
when I find them, Julia and Lua being different languages after all.
ā Of course =
isnāt legal in if
statements, at least without parentheses, in which case it doesnāt do the same thing as the other two. But none of this is confusing, because none of these are homonyms. You canāt say one while thinking it means the other. Someone coming from a language where the predicate in if
takes parentheses might typo a =
when they mean ==
, but this is one of the reasons the style guide says not to do that.
Lack of confusion is largely an artifact of our prior familiarity with languages which use these constructs. Imagine being a kid again, learning your first programming language: to the untrained eye, (a=b)
is nearly indistinguishable from (a==b)
āI know I messed it up. I think weāre just over-indexing on homonyms here, when there are plenty of ways code is confusing.
However you slice it, the visual āhamming distanceā between declarations, assignments, and equality tests isnāt large (unless we stop using the =
character). However, the let
and set
statements make the intent to assign in plain English, which is nice to readers.
Also, I think your fear of bugs in this case is overblown. Most of the time, assignments are on their own line and their return value isnāt of interest; if a comparison is seen on its own line within a basic block, not assigned and not returned so itāll be elided, we could throw a warning or error (since you probably wanted to assign the variable). Iād also be tempted to make let
and set
statements return nothing
to encourage them to be placed on their own line; assignments embedded in clever one-liners are clever, but not nice to readers. (Iād still allow multiple assignments, e.g. let a=b=1
, but let
doesnāt need to have a return value.)
Another attractor is that if we wish to add a new keyword sometime later, it can simply be added. For example, maybe someday in the future you want let dynamic a=1
for dynamically-scoped variables: you could simply decide dynamic
is a context-dependent keyword now and make it happen.
something x = y
for all values of something
other than global
, local
, and (top-scope) const
, is a parse error, so adding dynamic
or any other keyword there is already possible. global
/local
/const
could be context-dependent rather than lexer keywords. This would involve a token of lookahead, but thatās easy enough. They donāt happen to be, which is also fine, but means that if they became contextual, it wouldnāt invalidate any existing programs, you canāt have global = 5
, let alone global global = 5
. But these constructs could certainly be parsed if that was desired. It would be an unusual choice, but I kind of like it.
By contrast, having let
as a variable modifier would be undecidable in linear time, since let x = y
begins a let
block in Julia. Youād need to scan the entire program to count end
tokens and see if you came up short, for every let
, which is awful. I understand that you have a solution to this but donāt really care to relitigate that particular disagreement.
Iām repeating myself, but Iād use with...end
to serve the role of let...end
, and repurpose the keyword let
for single-line declaration/assignment statements.
I hope this does not count as reviving a dead thread.
First terminology: Thereās variable declaration, assignment, and variable definition, which I define as declaration and assignment (possibly with a single operator).
A non-breaking way to use :=
, as also mentioned by @mkitti and @mnemnion, is to use it for variable definition and have it throw an error if the variable is already in scope. I think this is a good compromise for those of us (I feel the pain @jar1 ) who are concerned about hard-to-catch bugs introduced when =
inadvertently reassigns to an existing variable.
With such a :=
, there is no āspooky action at a distanceā, no having to think too hard about variable names when you donāt need to.
Is there any opposition to using :=
as non-breaking definition operator on grounds OTHER THAN aesthetics?
Just throwing my 2 cents in to say, I like Odinās use of :=
as a ternary operator.
x : Float64 # declare
x : Float64 = 3.14 # declare and initialise
x := 3.14 # infer type from literal
x = 3.14 # reassignment throws if not yet declared
On a tangential note, their loop semantics are nice as well.