How are functors useful?

Interesting. This may just be an implementation detail — internally closures are lowered into a struct with a field for each capture:

https://github.com/JuliaLang/julia/blob/236df47251c203c71abd0604f2f19bf1f9c639fd/src/julia-syntax.scm#L2742-L2751

So while you can currently get the captures out by name (and can even introspect them with fieldnames) I’m not sure it’s going to stay this way.

trying to repost here…

sure here it is, it is not very styled as it is not public

fprint_runtime(g,n) = begin
    acum = 0.0
    counter = 0
    last_print_time = time()
    (args...) -> begin
        t = time()
        ret = g(args...)
        acum += time() - t;
        counter += 1
        if (t - last_print_time > n)
            avg = round(1e3*acum/counter)
            print("Average runtime of $(counter) runs : $avg ms\n")
            last_print_time = time()
            acum = 0.0
            counter = 0
        end
        ret
    end
end
export fprint_runtime

it accepts g a general function and n the minimal amount of seconds between console runtime logging,
and return a function that behaves like g that also logs its own runtime.

3 Likes

But x isn’t in the scope at all:

julia> x
ERROR: UndefVarError: x not defined

You are calling

getproperty(f, :x)

to extract a value via an implementation detail:

julia> fieldnames(typeof(f))
(:x,)

You are calling getproperty(f, :x) to extract a value via an implementation detail

Right. It’s just that here is the first place I’ve found out that there is a function to access the list of captured symbols and their values for a given closure.

This is documented in

https://docs.julialang.org/en/v1/devdocs/functions/#Closures-1

but, most importantly, you are talking about internals of the language, not something you should rely on in normal usage.

If you want the fields, really do use a struct. Otherwise, future changes to the internal representation could easily break your code — for example, the compiler could change the names of the variables closed over at any point.

3 Likes

Or decide on different types for the fields of the closure. Or decide that they will not change whereas that may not previously have been figured out.

2 Likes

I know this is a late reply, but I thought I add some advantages about functors I did not see mentioned in this thread.

I have implemented various algorithms in both approaches. Sometimes I have some struct which must be passed in to select correct algorithm and parameters, while other times I use a functor.

Say you use the non-functor approach. Your calls would be something like f(a, x) where a is a parameter used by the algorithm and x is the input. With a functor it is is more like f = F(a); f(x). The benefit of the former is that sometimes you want multiple operations operating on the same structure. E.g. one encryption and one decryption algorithm. In this case functors are not much of an advantage I suppose.

However the benefit of being able to call a function like f(x) where x is the input is because function composition tends to be a lot easier to accomplish with single argument function. Or function composition at least benefits from function only taking inputs and not parameters controlling the operations of the algorithm. It makes it easier to chain function calls with |>, to use them with map, broadcast, filter etc.

Functions which only take inputs and not parameters are more compatible with various libraries. It provides a more universal interface. It is a bit like Unix pipelines. The power comes form a simple standardised universal interface that a lot of programs can adhere to.

12 Likes