Can a function returns a partial function?

Hi,
I am new to Julia coming from Python. In Python we have the “partial” function that allow to derive a function by fixing some arguments. Can we do something similar in Julia?
I would like to do something like:

function f(x, y, z)
x + y + z
end

and then create a function g(x) = f(x, y=computed_y(), z=computed_z())

I can obviously define f as f(x; y…, z=…) and then call f(x) but I have the feeling that computed_y and computed_z are called very time I call f while I would like them to be computed once only.

1 Like

How about this?

f(x, y, z) = x + y + z

computed_y() = (println("y_comp"); 2)
computed_z() = (println("z_comp"); 3)

g = let y, z
    y = computed_y()
    z = computed_z()
    g(x) = f(x, y, z)
end

g(1) == 6

Maybe a little bit awkward.

1 Like

See this discussion Is there a package to that can make partial functions automatically?

I think this is much better:

f(x, y, z) = x + y + z

computed_y() = (println("y_comp"); 2)
computed_z() = (println("z_comp"); 3)

@eval g(x) = f(x, $(computed_y()), $(computed_z()))

g(1) == 6

You should not need eval for this. A closure is fine, eg

f(x, y, z) = x + y + z
y, z = 2, 3
g = x -> f(x, y, z)
g(1)
1 Like

The reason is…@tog seems to want one liner, plus he seems to like to know how to interpolate computed_y() stuff into the arguments once and for all, not every time of invocation. Practically, you are right. :slight_smile:

I am not sure I understand what is meant here.

When we define g(x) in this way:
g(x) = f(x, computed_y(), computed_z())

Everytime we call g(x), computed_y() and computed_z() will have to be calculated.

What if we seek for an expression that looks very much like g(x) = f(x, computed_y(), computed_z()) but that takes the values of computed_x() and computed_y() only at the time of defining g(x)? I thought this was what @tog was really curious about, and @eval g(x) = f(x, $(computed_y()), $(computed_z())) may be close to what he looks for.

You definitely don’t need and should not use eval for this, a simple closure will do. Eg

let y, z = computed_y(), computed_z()
    x -> f(x, y, z)
end

or similar.

4 Likes

I agree eval should be used with care since its evaluation goes into global scope. Definitely the kind of expression I suggested should be discouraged.

1 Like

@Sijun and @Tamas_Papp
I think you have understood what I want to do i.e not recompute y & z each time the one argument instance is called.
I am not sure I understand what you meant by global scope. I am not sure it is a good idea to make visible to new variables y & z as proposed initially by @Tamas_Papp
I need to understand the let construct does it create the y & z variables and leave only visible inside the let ? are they recomputed each time I call g(something) ?

Yes, that’s right. For example:

julia> let x = 1, y = 2
         println("inside the let block, we can see x and y: ", x, " ", y)
       end
inside the let block, we can see x and y: 1 2

julia> x
ERROR: UndefVarError: x not defined

julia> y
ERROR: UndefVarError: y not defined

No, and we can show it by making the computed_x and y function print something every time they are called, as @Sijun did.

For example:

julia> f(x, y) = x + y
f (generic function with 1 method)

julia> function computed_y()
         println("computing y")
         2
       end
computed_y (generic function with 1 method)

julia> const y = computed_y()
computing y
2

julia> g(x) = f(x, y)
g (generic function with 1 method)

julia> g(1)
3

Notice how computing y is only printed once when we define the variable y. If you want to avoid making the y variable visible, you can hide it in a let block, as suggested above:

julia> const g2 = 
         let y2 = computed_y()
           x -> f(x, y2)
         end
computing y
#3 (generic function with 1 method)

julia> g2(1)
3

Again, computing y is only printed once, when you define the function g2.

And in this case, y2 is only visible inside the let block:

julia> y2
ERROR: UndefVarError: y2 not defined
2 Likes

Hi @tog, welcome. Regarding your question:

Yes, you can create the function g(x). However, in Julia, this is called a closure and you can check what it does in the documentation, here.

You can read about global scope in here, it is important.

Once you figure out the global scope meaning, you will see that the closure must not use global variables, because of performance issues (see here).

Following all these documents you will see why @Tamas_Papp example is correct:

Or why @rdeits is using const:

etc…

Correctly using closure in global scope to get Python semantics of default arguments is rather cumbersome as you need to use the global keyword

let y, z
    y = computed_y()
    z = computed_z()
    global g(x) = f(x, y, z)
end

Note that, without global, you can’t add methods to g.

Comparing this to @eval g(x) = f(x, $(computed_y()), $(computed_z())), I don’t think it’s necessary bad to use @eval, provided that you understand why you need it.