Nathan Davis
Nathan Davis

Reputation: 5766

How can I write higher-order function to add methods to a given function?

Let's suppose I want to write a function that accepts any associative operator ⊕ and adds methods to it such that I can replace any value with a function. The semantics of these additional methods are as follows:

I can express this in code as:

associative!(⊕) = begin
    ⊕(f::F, y) where F<:Function = (xs...) -> f(xs...) ⊕ y
    ⊕(x, g::G) where G<:Function = (xs...) -> x ⊕ g(xs...)
    ⊕(f::F, g::G) where {F<:Function, G<:Function} = (args...) -> f(args...) ⊕ g(args...)
    ⊕(f::F, y, zs...) where F<:Function = f ⊕ ⊕(y, zs...)
    ⊕(x, g::G, zs...) where G<:Function = x ⊕ ⊕(g, zs...)
    ⊕(f::F, g::G, zs...) where {F<:Function, G<:Function} = f ⊕ ⊕(g, zs...)
end

However, when I try to compile this function I get the following error:

ERROR: syntax: cannot add method to function argument ⊕

I know I can write a higher-order function that returns a new function / operator that is based on the one given. For example:

associative(⊞) = begin
    let ⊕(x) = x
        ⊕(x, y) = x ⊞ y
        ⊕(x, y, zs...) = ⊕(x⊞y, zs...)
        associative!(⊕)
        ⊕
    end
end

If I inline the definition of associative! here then associative works just fine, and I can write:

⊕ = associative(+)
⊗ = associative(*)

f(x) = 3⊗x ⊕ 1
f(1) # 4
f(cos)(0) # 4

But I thought it would be nice to have a mutating version as well. I assume re-writing associate! as a macro would work, but there really doesn't seem to be anything that would necessitate the use of a macro here. So, is it possible to do this as a higher-order function and, if so, how?

Upvotes: 2

Views: 632

Answers (2)

Nathan Davis
Nathan Davis

Reputation: 5766

There are a few things going on here. The first thing to note is that the first instance of ⊕(<args...>) = <body...> will create a new function and bind it to . However, it apparently does not create a new binding for . I'm guessing that's why the error message mentions is a function argument, but this is a red herring anyway — it would simply create a new function, rather than mutate the existing one.

Toward the end of this thread, we see our first hint at a solution. In order to dynamically add a method to a function alias, we basically need to use the same syntax we'd use to define a callable object for some arbitrary datatype:

(::typeof(⊕))(<args...>) = <body...>

With this change, we get a different error:

Global method definition around ... needs to be placed at the top level, or use "eval"

A little further in the same thread, we see that this is due a change sometime after Julia 0.6, and that we now need to @eval these expressions. Now that we're involving @eval, we need to keep the following in mind:

  • We need to splice our references to using $⊕
  • The $ in front of seems to interfere with Julia recognizing it as an infix operator. So we need to use the functional (prefix) notation instead.

Putting this together, we end up with something like:

@eval (::typeof($⊕))(f::F, y) where F<:Function = (xs...) -> $⊕(f(xs...), y)

If we really want to use infix notation instead, we can use a let-binding inside @eval:

@eval let ⊕ = $⊕
    (::typeof($⊕))(f::F, y) where F<:Function = (xs...) -> f(xs...) ⊕ y
    .
    .
    .
end

Upvotes: 0

xiaodai
xiaodai

Reputation: 16004

I don't know how to write your symbol but doesn't this get you someway towards what you want?

import Base.+

+(f::Function, g::Function) = x -> f(x) + g(x)

+(f::Function, x) = f(x) + x

# e.g.
new_plus = +(sum, sum)

new_plus([1,2,3]) # gives 12

Upvotes: 1

Related Questions