Как я могу написать функцию высшего порядка для добавления методов к данной функции? - PullRequest
2 голосов
/ 21 марта 2019

Предположим, я хочу написать функцию, которая принимает любой ассоциативный оператор ⊕ и добавляет к нему методы, так что я могу заменить любое значение на функцию.Семантика этих дополнительных методов следующая:

  • Если оператор затем применяется к любым двум функциям f и g, результатом должна быть функция, которая сначала применяет f иg (независимо) к своим аргументам и затем применяет ⊕ к результатам.
  • Если один аргумент является функцией f, а другой - любым нефункциональным значением x, результатом является функциякоторый сначала применяет f к своим аргументам, а затем применяет ⊕ к результату и x.

Я могу выразить это в коде как:

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

Однако, когдаЯ пытаюсь скомпилировать эту функцию, я получаю следующую ошибку:

ERROR: syntax: cannot add method to function argument ⊕

Я знаю, что могу написать функцию высшего порядка, которая возвращает новую функцию / оператор, основанный надано.Например:

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

Если я приведу здесь определение associative!, тогда associative прекрасно работает, и я могу написать:

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

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

Но я думал, что это будетПриятно иметь и мутантную версию.Я предполагаю, что переписать associate! как макрос будет работать, но на самом деле, похоже, нет ничего, что потребовало бы здесь использования макроса.Итак, возможно ли сделать это как функцию более высокого порядка, и если да, то как?

Ответы [ 2 ]

1 голос
/ 21 марта 2019

Я не знаю, как написать ваш символ, но разве это не приведет вас к тому, что вы хотите?

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
0 голосов
/ 22 марта 2019

Здесь происходит несколько вещей.Первое, что нужно отметить, это то, что первый экземпляр ⊕(<args...>) = <body...> создаст новую функцию и свяжет ее с .Однако, по-видимому, это не создает новую привязку для .Я предполагаю, что именно поэтому в сообщении об ошибке упоминается - аргумент функции, но в любом случае это красная сельдь - она ​​просто создаст новую функцию, а не поменяет существующую.

К концуиз этой темы , мы видим наш первый намек на решение .Чтобы динамически добавить метод к псевдониму функции, нам в основном нужно использовать тот же синтаксис, который мы использовали бы для определения вызываемого объекта для некоторого произвольного типа данных:

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

С этим изменением мы получаемдругая ошибка:

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

Чуть дальше в том же потоке мы видим, что это происходит из-за изменения через некоторое время после Julia 0.6, и что нам теперь нужно @eval эти выражения,Теперь, когда мы задействуем @eval, нам нужно помнить следующее:

  • Нам нужно соединить наши ссылки на , используя $⊕
  • The $ перед , кажется, мешает Джулии распознавать его как инфиксный оператор.Поэтому вместо этого нам нужно использовать функциональную (префиксную) нотацию.

Соединив это, мы получим что-то вроде:

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

Если мы действительно хотим использовать инфиксную нотациювместо этого мы можем использовать let -связку внутри @eval:

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