I’m sure I am missing something simple here so I apologize in advance. But I am a little confused as to why this first assignment works.
using Dates
julia> function currentmin(minute = minute(now()))
minute
end
currentmin (generic function with 2 methods)
julia> currentmin()
22
whereas this one returns an error
julia> function currentmin1()
minute = minute(now())
end
currentmin1 (generic function with 1 method)
julia> currentmin1()
ERROR: UndefVarError: minute not defined
According to the documentation on scope, functions introduce a Hard scope: If x is not already a local variable and assignment occurs inside of any hard scope construct (i.e. within a let block, function or macro body, comprehension, or generator), a new local named x is created in the scope of the assignment.
I thought maybe the assignment worked in currentmin because minute technically hadn’t been called until the assignment was made. But then why doesn’t it work for currentmin1?
Even if the arguments of the function are being made outside the hard scope of the function shouldn’t the assignment still fail because of the global scope of the module?
using Dates
julia> minute = 45
ERROR: cannot assign a value to variable Dates.minute from module Main
As far as I understand you can interpret the function parameter as this:
julia> x = 1
1
julia> f = let x = x
x += 1
end
2
julia> x
1
# the same as:
julia> x = 1
1
julia> g(x=x) = x += 1
g (generic function with 2 methods)
julia> g(x)
2
julia> x
1
meaning that the left side of the parameter assignment defines a variable local to the scope of the function, the right side is the global (or outer scope) variable. Thus, in your first function, the left side minute is a variable local to the scope of the function, the right side minute is a reference to the global minute function.
In your second example there is an ambiguity which raises an error, because you are assigning a local variable name and trying to call a function with the same label.
This is a well written explanation, which I was searching for yesterday, but all I was able to create was overly complex and even not understandable by myself
where only local identifiers minute exist, with the ambiguity of it.
The parser can do it right, as you probably expected it, with e.g.:
julia> function currentmin2()
m = minute(now())
end
julia> @code_lowered currentmin2()
CodeInfo(
1 ─ %1 = Main.now()
│ %2 = Main.minute(%1)
│ m = %2
└── return %2
)
Still, there is the question, that the parser could do it right for currentmin1() because the first usage of identifier minute must be the function Main.minute(...), so why isn’t it happening like that?
I don’t know if it is as it is by purpose, but I think it is good, that an error is raised, because more complex functions can easily be a nightmare to debug if the parser tries to be overly smart in such cases.
Thanks! Really appreciate both your explanations! It took me awhile to really understand the dynamic but it really clears up a lot in terms of how the function arguments are being defined.
@oheil just to clarify I am reading this correctly when @code_lowered returns an identifier in parenthesis followed by the identifier without as in as in (minute),minute here
this means that there is an ambiguity which will raise an error?
The paranthesis around (minute) don’t mean anything, it’s the same as
minute(%1)
and means call to function minute(...). Don’t know why they are there.
The ambiguity is not visible here, but the parser has seen the ambiguity and has decided to put a
minute(...)
instead of
Main.minute(...)
because, and this is a guess, the parser choose to use all identifiers minute as local, as it is a local variable, despite the obvious call to a global function. This is not mandatory, as you can see in currentmin2(), but it would mean, that the parser would need to differentiate between identifiers as variables and identifiers as functions. I don’t know if it is differentiated or how parsing works in this case.
Anyways, the outcome is good in this case, as it helps to avoid using identifier minute as a variable name here.
By the way, the Dates API is more to using minute() as Dates.minute(). This is the way as it is used throughout the documents. The best way is sticking to the docs, like: