Although I can easily construct an operator algebra in Julia using closures, I wonder if this is the best/most “Julian” way of doing it. As an example, take the following code
import Base: +, -, *, /
abstract type AbstractOperator end
#Some concrete operator type:
struct MyOperator{T} <: AbstractOperator
x::T
end
MyOperator() = MyOperator(rand())
(f::MyOperator)(x) = f.x + x
#Operations on operators:
struct OperatorOperation{O, F<:AbstractOperator, G<:AbstractOperator} <: AbstractOperator
op::O
f::F
g::G
end
(h::OperatorOperation)(x) = h.op(h.f(x), h.g(x))
for op ∈ (:+, :-, :*, :/)
@eval Base.$op(f::AbstractOperator, g::AbstractOperator) = OperatorOperation($op, f, g)
end
After which I can do
op = MyOperator() #Make an operator
op(1.0) #Evaluate for some input.
op = MyOperator() + MyOperator(1) #Make a slightly more involved operator
op(1.0) #Evaluate for some input.
op = let #An even more involved operator
op = MyOperator()
for i = 1:100
⋆ = rand((+,-,*,/)) #Randomly select binary operations, just to make the case non-trivial.
op = op ⋆ MyOperator()
end
op
end
op(1.0) #Evaluate for some input.
All of the above works just fine, but the (massive) type signature* of the last example made me wonder if this approach is the recommended way to go in Julia… Or is there some better pattern to use**?
*Sure, I can make my own pretty printing of AbstractOperator
, but that’s beside the point…
**Sure, the example above could (and should) be optimized by e.g. combining the sum of two MyOperator
s into one, but that’s also somewhat beside the point…