Why can't methods be defined inside a `let` block?

When one runs the following code, my expectation is that three methods would get defined.

# Default equal function:
preferred_equal_fn(x) = (===)

# Numbers use isequal
let x = isequal
    preferred_equal_fn(::Type{T}) where {T <: Number} = x
end

# So do strings.
begin
    local x = isequal
    preferred_equal_fn(::Type{T}) where {T <: AbstractString} = x
end

However, the let block seems to prevent the definition:

methods(preferred_equal_fn)
# [Out] :=  #2 methods for generic function preferred_equal_fn:
#                preferred_equal_fn(::Type{T}) where T<:AbstractString in Main at In[1]:12: 
#                preferred_equal_fn(x) in Main at In[1]:2

What’s going on here? Is this documented anywhere?

let creates a “hard” scope, see Scope of Variables · The Julia Language.

To define a method in a let block, you need to manually use the global keyword:

# Numbers use isequal
let x = isequal
    global preferred_equal_fn(::Type{T}) where {T <: Number} = x
end
7 Likes

Thanks; that the global keyword fixes this seems intuitive. I seem to be a bit confused about the specific terminology, though. The documentation says that that begin blocks do not introduce a new scope, but it seems that they do respect local definitions, which I had taken to indicate a scoping layer.

x = 20
begin
    local x = 10
    println(x)
end
println(x)
# [Out] := 10
# [Out] := 20

Am I correct in thinking that soft and hard scopes actually have an effect on whether keywords like local are implicitly prepended to variable assignments inside them, while begin blocks form a kind of very-soft scope that doesn’t prepend anything?

julia> x = 20
20

julia> begin
        x=20
        println(x)
        end
20

julia> x
20

julia> let x = 10
       print(x)
       end
10
julia> x
20

I hope this helps you to clear the view, basically, your begin + local is the same as a let block with local variables, and if you don’t have local, then you can directly change x inside the begin I think

I think this is more to do with the fact that local in global scope acts a little funny. E.g., you can do:

julia> x = 20
20

julia> local x = 10
10

julia> x
20

julia> (begin
       local x = 10
       end; println(x)); println(x)
10
20

It’s not the begin block that’s introducing the scope, it’s the evaluation of the complete expression itself.

Edit: This is issue #10472.

7 Likes

Interesting…

julia> local x = 10
10

julia> x
ERROR: UndefVarError: x not defined

So, begin + local only allow one to reference that local name.
Cf

julia> let
           local x = 5
           begin
               local x = 10
               println(x)
           end
           println(x)
       end
10
10