Example of generated functions -- suitable for docs?

I have a good introductory example of the need for generated functions using multiplication of two objects of type Vol{n} type representing an n-dimensional volume (see below for the code).

This could replace the part in the Julia manual that does things that don’t need generated functions, after which we are then told “Don’t copy these examples!” ! However, I am concerned that the level of the mathematical discussion (n-dimensional volumes) might just confuse the issue. The question is thus if it is suitable for the manual?
Of course, you can just give the code without the mathematical motivation, or replace the + with a * and treat it as a way to “put multiplication into the compile stage”.

immutable Vol{N}
    volume::Float64
end

Instead of

import Base.*
*{N1,N2}(V1::Vol{N1}, V2::Vol{N2}) = Vol{N1+N2}(V1.volume * V2.volume)
* (generic function with 150 methods)

we should use

@generated *{N1,N2}(V1::Vol{N1}, V2::Vol{N2}) = :(Vol{$(N1+N2)}(V1.volume * V2.volume))

to get efficient code.

What about a blogpost on generated functions where you explain the math and the code? This sounds like something I’d like to read in detail.

Though I agree that the example for generated functions should be changed in the manual. I find the current one to be a brick wall (I still haven’t used generated functions because of it, though by now I probably should have).

Why do you think this is a good example? It doesn’t need generated functions to get multiplication of constants to happen in the compile stage (that just happens naturally without any effort on your part), while the transform that you did in the example above actually adds lots of confusing, subtle, and an unnecessary limitations. I won’t go into those here, but for example, we can just write it as:

immutable Volume
  volume::Float64
  ndims::Int
end
import Base.*
*(V1::Volume, V2::Volume) = Volume(V1.volume * V2.volume, V1.ndims + V2.ndims)

which has the same runtime constant-folding capability, without the memory and compile-time overhead and in many cases will generate better, more efficient code.

Even if this isn’t the right choice, I think there should be a discussion about making a new and simpler (and correct) example for the manual.

1 Like

The proposed example demonstrates how the understanding of “how something works” (here generated functions) isn’t the same with the understanding of “how something is useful”. Generated functions are a recent addition to Julia and a naturally complicated one. The author of the respective section in the manual included everything he should to cover the working part (the “Don’t copy them!” examples actually help in that effort), but the usefulness part (especially how to improve things, instead of worsening them, whether performance-wise or readability-wise etc.) has much room for improvement (only a loop unrolling example is given). However, I don’t have to offer a better alternative at the moment (I’m currently working on simpler concepts).

How can you be sure in this case that the addition of the ndims happens at compile time? If I understood correctly, the OP example was to get compile-time computation of the dimensionality, i.e. the N1 + N2?

My example was too abbreviated. The other part of it is that you should only be able to add two Vol objects if they have the same dimension:

+{n}(V1::Vol{n}, V2::Vol{n}) = Vol{n}(V1.volume + V2.volume)

In my actual use case, this is tightly coupled to actual objects that are N-dimensional, whose volume you are taking. The compiler will then actually be able to know at compile time what the dimensions all are.

An example of generated functions is very suitable for Docs Julia for sure, especially now that I am trying to understand what has been generated in Tensorflow.jl. David, we welcome you!

A generated function is not necessary in this situation. A Base.@pure function would suffice.

struct Vol{N}
    volume::Float64
end

Base.@pure producttype{N1,N2}(::Type{Vol{N1}}, ::Type{Vol{N2}}) = Vol{N1 + N2}
import Base.*
x::Vol * y::Vol = producttype(typeof(x), typeof(y))(x.volume * y.volume)

Admittedly, I don’t fully understand the criteria needed for a function to be @pure; perhaps @jameson can confirm that this is correct.

1 Like