Operations on expressions

HI,
is there a reason why there are no operations such as addition or subtraction defined on symbols and expression. I mean something like :(x*y) + :z = :(x*y + z). I just wrote down a few things like that (see https://github.com/timkittel/SymbolicMath.jl where I used a wrapper to avoid type piracy) because I need it to symbolically evaluate some functions without heavy dependency. Afterwards, I realized it might make sense to simply define that on the expression itself. Is there a reason why this is not a good idea? Or should I maybe simply open a PR?
Cheers,
Tim

1 Like

No. Having a meaning in a specific context/application never means that it is a good idea in general even though most people tend to think this way.

For this case in particular, operation on expressions simply doesn’t make sense for what symbols and expressions are designed for, which is an accurate representation of a julia expression. The transformation you are suggesting misses a lot of information (e.g. about scope).
Doing this on symbols will also be really confusing since they are very similar to strings but the operation you suggest will be very different from string ones.
Finally, doing this only on operators is really arbitrary and doing this for all functions will make it impossible to define any functions that operate on expressions/symbols.

2 Likes

okai, that makes sense. So it actually makes sense to put in a wrapper and then operate on the level of that wrapper as it was designed for that?

1 Like

Yes.

Thank you!

You can do operations on expressions using my package

For example

julia> using Reduce
Reduce (Free CSL version, revision 4590), 11-May-18 ...

julia> @force using Reduce.Algebra

julia> :(x*y) + :z
:(x * y + z)

julia> int(ans,:y)
:(((x * y + 2z) * y) / 2)

With the ForceImport package the overloaded operations are defined separately from the Base methods, so that they can remain local in a module scope without changing the behavior of global methods.

Oh interesting, I will check it out. But What was your reason for using plain expressions instead of a wrapper? It kinda goes against the logic explained by @yuyichao (which makes a lot of sense to me).

That’s right, we have somewhat of a disagreement about this, but for my purposes it achieves what I wanted and my solution does not interfere or cause any issues.

My goal is to manipulate the Julia AST, I didn’t want to add extra translation layers for a wrapper. I figured the AST itself provides plenty of information, although with a special wrapper type you could encode extra data. Mainly, I just wanted to take this idea of having operations on Symbol to see how far it can be taken while maintaining compatbility and flexibility. However, that’s not the main purpose, I only did it because it is possible, but if you don’t want this feature, then don’t use the command @force using Reduce.Algebra and then you don’t have to use those operations.

It limits the scope so it won’t cause much trouble. I wouldn’t recommand import that in a package that does anything else though since it could be hard to understand.

I also don’t necessarily recommend it since it might be confusing to apply this technique of managing the scope in other packages, although I will gladly explain how to correctly use it if requested.

With a bit of delay, @chakravala, I would be happy if you could demonstraite the correct use (:

The principle which all @force users must keep in mind is this: the goal is to extend Base methods locally without affecting the global method table by type piracy, and to be able to import them into another package to have the same local effect. In order to avoid the type piracy, one must define a new local complementary n-ary method that will fall back on the base method with Any arguments. Then there will be a tiered alternative dispatch layer within the local package scope that redirects to the default Base dispatch generically. This has many advantages, including a 2x-improvement in precompile time (since the precompilation is now tiered), which actually makes a big difference if this technique is applied to a large number of operations (on the order of 50-100 in Reduce).

module ExtendedPackage
+(x...) = Base.:+(x...)
+(x::Symbol,y::Number...) = Expr(:call,:+,x,y...)
end

Then you can use it locally with the @force macro, which automatically forces imports of all exported methods one by one;

module NewScope
using ForceImport
@force using ExtendedPackage
end

You will now have the property

julia> Base.:+ == NewScope.:+
false

where the + in the NewScope is now the extended plus that falls back on Base, which is also different from the + in Main. All it takes is the application of this principle, which has been pioneered with the development of the Reduce.Algebra module (defined in src/args.jl and src/unary.jl).

The main difficulty lies in properly designing the redirection from the tiered method dispatch so that the infix operations work naturally with a variety of syntax.

2 Likes

First of all, thanks a lot for the extensive explanation. Following your example, I am trying to implement a similar idea for getindex but it’s somehow not working (which is why I am responding so lat). I will will try to work up an minimal (not) working example and maybe you could help me a bit with the force import.

Again, thanks for the explanations!

1 Like