How to make a function factory

I’m looking into rewriting parts of my VoronoiCells package. One thing I want to do is to have a type Rectangle and make functions that maps points between two Rectangles.

This attempt appears to work:

struct Rectangle
    Left::Float64
    Right::Float64
    Lower::Float64
    Upper::Float64
end

left(r::Rectangle) = r.Left
right(r::Rectangle) = r.Right
lower(r::Rectangle) = r.Lower
upper(r::Rectangle) = r.Upper

function construct_rectangle_map(from::Rectangle, to::Rectangle)
    function rect_map(p::GeometricalPredicates.Point2D)
        GeometricalPredicates.Point2D(
            left(to) + (getx(p) - left(from)) / (right(from) - left(from)) * (right(to) - left(to)),
            lower(to) + (gety(p) - lower(from)) / (upper(from) - lower(from)) * (upper(to) - lower(to))
        )
    end
end

The getx and gety are from the GeometricalPredicates package.

With from = Rectangle(-1, 1, 0, 1) and to = VoronoiCells.Rectangle(1, 2, 1, 2) I get a map m = construct_rectangle_map(from, to) that seems to work: m(Point2D(0.3, 0.4))

However, looking at @code_lowered m(Point2D(0.3, 0.4)) it appears that the the left/right are called inside the function.
I think it would be nice if the numbers are saved as the constants they are for a given map. Does anyone know how to do this?

Thanks!

1 Like

left and right will get inlined to struct accesses when it is compiled, but not to constants. In principle, there are ways to inline them to constants, but trying to have a separate compiled function for large numbers of rectangle pairs is likely to do more harm than good.

I would normally suggest just having a rect_map(from, to, p) function that takes 3 arguments. In any context where you need a single-argument function you can do p -> rect_map(from, to, p) … you don’t need a “function factory” pattern for this because construct_rectangle_map is neither shorter nor more convenient than p -> .... (At some point in the future we might even have the syntax rect_map(from, to, _).)

In the unlikely event that the cost of rect_map is the performance-critical step in your code, you can do further optimizations, but I would wait until profiling makes that clear.

5 Likes

Thanks! I think your suggestion with a 3 argument rect_map sounds like a good plan.