Functions as fields of mutable struct in if-elseif environment


#1

Hi,
so I’m trying to do define functions as fields of a mutable struct based on input parameters. The general structure of which has the following form

module TestModule
  mutable struct TestStruct
    F
    function TestStruct(args...)
        f(x) = x;
        if length(args)==0
            println("0 args")
            f(x)  = x
            println("The value of f(x)=x at x=1.0 is $(f(1.0))")
            return new(f)
        elseif length(args)>0
            println("at least 1 arg")
            f(x) = -x;
            println("The value of f(x)=-x at x=1.0 is $(f(1.0))")
            return new(f)
        end
        end
    end
end

which seems to produce the wrong value for f(x), in the sense that

c=MyModule.MyStruct();

produces

0 args
The value of f(x)=x at x=1.0 is -1.0

which is wrong, and, e.g.

c=MyModule.MyStruct(1);

gives

at least 1 arg
The value of f(x)=-x at x=1.0 is -1.0

However, if I remove all occurrences of elseif , I get a correct assignment of c.F function to f(x) = x, i.e.

module TestModule
  mutable struct TestStruct
    F
    function TestStruct(args...)
        f(x) = x;
        if length(args)==0
            println("0 args")
            f(x)  = x
            println("The value of f(x)=x at x=1.0 is $(f(1.0))")
            return new(f)    
        end
        end
    end
end

Is this behavior intentional? am I’m doing weird design flops?
how do I get the field F of a mutable struct to be assigned the correct function in the ifelse environment?
Thanks!


#2

Use anonymous functions for f (f = x -> x and f = x -> -x), see e.g. Defining a function inside if...else..end NOT as expected?. There is also an issue about this that I can’t find now.


#3

Yeah, this is an (in my opinion, unfortunate) consequence of Julia’s design. It’s not necessary to have a struct definition involved to see the issue, you can simply do:

julia> function foo()
         f() = 0
         if true
           f() = 1
         else
           f() = 2
         end
         return f
       end
foo (generic function with 1 method)

julia> f = foo()
f (generic function with 1 method)

julia> f()
2  # nooooooooo

Basically, inside a function, all of those f() = whatever definitions get compiled before a particular branch of the if-else statement is chosen, and they all modify the definition of the same function f(). f() can only do one thing, so it ends up doing whatever the last definition was, even if that branch of the if-else will not be chosen at run-time.

Fortunately, the fix is very simple: if you use anonymous functions, you can make sure that each branch of the if-else returns a different, correct, anonymous function:

julia> function foo()
         if true
           f = () -> 1
         else 
           f = () -> 2
         end
       end
foo (generic function with 1 method)

julia> f = foo()
#7 (generic function with 1 method)

julia> f()
1  # what you expected

#4

I really appreciate your reply, fixed my issue, saved me time. Always super helpful.
Many thanks!