Stream: helpdesk (published)

Topic: Named functions in a let


view this post on Zulip Philipp Gabler (Dec 05 2021 at 11:46):

What's going on here? Appearently the last named function in the let is treated as a toplevel function definition, but the second is not... on the other hand, the first assignment only points to the second method. What is the underlying logic of this?

julia> let fn = function bar3(i, x) x + 2 end, fn2 = function bar3(x) fn(x) end
           fn2
       end
(::var"#bar3#14"{typeof(bar3)}) (generic function with 1 method)

julia> (let fn = function bar3(i, x) x + 2 end, fn2 = function bar3(x) fn(x) end
           fn2
       end)(1)
ERROR: MethodError: no method matching bar3(::Int64)
Closest candidates are:
  bar3(::Any, ::Any) at REPL[35]:1
Stacktrace:
 [1] (::var"#bar3#15"{typeof(bar3)})(x::Int64)
   @ Main ./REPL[35]:1
 [2] top-level scope
   @ REPL[35]:2

julia> methods(bar3)
# 1 method for generic function "bar3":
[1] bar3(i, x) in Main at REPL[35]:1

julia> bar3(true, 1)
3

julia> f = let fn = function bar4(i, x) x + 2 end, fn2 = function bar4(x) println("hi"); fn(x); end
           fn2
       end
(::var"#bar4#17"{typeof(bar4)}) (generic function with 1 method)

julia> f(1)
hi
ERROR: MethodError: no method matching bar4(::Int64)
Closest candidates are:
  bar4(::Any, ::Any) at REPL[40]:1
Stacktrace:
 [1] (::var"#bar4#17"{typeof(bar4)})(x::Int64)
   @ Main ./REPL[40]:1
 [2] top-level scope
   @ REPL[42]:1

view this post on Zulip Andrey Oskin (Dec 05 2021 at 14:35):

What are you trying to achieve? fn2 declaraion is incorrect, since fn(x) is not defined.

view this post on Zulip Andrey Oskin (Dec 05 2021 at 14:40):

Ok, may be it's slightly different issue, but you can see similar error, even if you change named functions to anonymous

julia> (let fn = (i, x) -> x + 2, fn2 = x -> fn(x)
                  fn2
              end)(1)
ERROR: MethodError: no method matching (::var"#7#9")(::Int64)
Closest candidates are:
  (::var"#7#9")(::Any, ::Any) at REPL[4]:1

view this post on Zulip Philipp Gabler (Dec 05 2021 at 15:39):

Nothing really to achieve concretely, I just was thinking about something like this during writing a macro. And I don't understand the behaviour.

The issue is not with the missing method of fn (stupid me, I just forgot the first argument), but about why the named function creates a (globally visible) function at top level only with one method.

julia> f = let fn = function bar6(i, x) x + 2 end, fn2 = function bar6(x) fn(true, x) end
           fn2
       end
(::var"#bar6#34"{typeof(bar6)}) (generic function with 1 method)

julia> methods(bar6)
# 1 method for generic function "bar6":
[1] bar6(i, x) in Main at REPL[57]:1

julia> f(1)
3

julia> f = let fn = function bar7(i, x) x + 2 end, fn2 = function bar7(x) bar7(true, x) end
           fn2
       end
(::var"#bar7#35") (generic function with 1 method)

julia> f(1)
ERROR: MethodError: no method matching (::var"#bar7#35")(::Bool, ::Int64)
Closest candidates are:
  (::var"#bar7#35")(::Any) at REPL[61]:1
Stacktrace:
 [1] (::var"#bar7#35")(x::Int64)
   @ Main ./REPL[61]:1
 [2] top-level scope
   @ REPL[62]:1

julia> methods(bar7)
# 1 method for generic function "bar7":
[1] bar7(i, x) in Main at REPL[61]:1

Why is bar7 treated as a closure within the second part of the let-block, whereas the first method is leaked outside?

view this post on Zulip Philipp Gabler (Dec 05 2021 at 15:43):

This is on 1.7.0, BTW.

view this post on Zulip Philipp Gabler (Dec 05 2021 at 15:53):

In non-global scope, named functions don't work at all:

julia> function outer()
           f = let fun1 = function bar8(i, x) x + 2 end, fun2 = function bar8(x) fun1(true, x) end
               fun2
           end
       end
outer (generic function with 1 method)

julia> outer()
ERROR: UndefVarError: fun1 not defined
Stacktrace:
 [1] outer()
   @ Main ./REPL[70]:2
 [2] top-level scope
   @ REPL[71]:1

julia> f = let fun1 = function bar8(i, x) x + 2 end, fun2 = function bar8(x) fun1(true, x) end
           fun2
       end
(::var"#bar8#40"{typeof(bar8)}) (generic function with 1 method)

julia> function outer()
           f = let fun3 = ((i, x) -> x + 2), fun4 = ((x) -> fun3(true, x))
               fun4
           end
       end
outer (generic function with 1 method)

julia> outer()
#42 (generic function with 1 method)

julia> outer()(1)
3

I always thought that a named function at non-global scope behaved just like a closure with a specifed name (which gets mangled into the generated symbol), but appearently it does make a difference.


Last updated: Oct 02 2023 at 04:34 UTC