Reputation: 5766
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:
f
and g
, the result should be a function that first applies f
and g
(independently) to its arguments and then applies ⊕ to the results.f
but the other is any non-function value x
, the result is a function that first applies f
to its arguments and then applies ⊕ to the result and x
.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
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:
⊕
using $⊕
$
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
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