To define a function for squaring a Var and returning a Var, I could write
function square(var::Var)
y = square(var.x)
return Var(y)
end
However, if I have many other functions like square (cube, square_root, etc), how can I avoid rewriting the function for every function that uses Float64 as an input? I could think of a way in an OOP language by inheriting from an abstract class with undefined function, but not sure how to do this in Julia.
really only works on Float64 (because it manipulates them in a way that usually only works on Float64 specifically, like some bit-level manipulation) or
it’s limited unnaturally to Float64, even though it doesn’t require Float64 per se and rather depends on some other contract.
In the case you posted, square doesn’t actually care about Float64 at all - it only cares about having a pow method that takes as it’s first argument something and as its second argument an Integer. So in this case, square is erronously limited to Float64.
I’d solve this by
not limiting my methods arbitrarily
implementing ^ (and whatever else you may need, I think there are some packages that can do this kind of forwarding for wrapper types almost automatically)
Then, square(x) with typeof(x) === Var just works™. Moreover, because julia compiles specialized methods for each combination of input argument types, this will make your code much more composable and still keep performance. For example, not limiting your methods to ::Float64 would allow something like DifferentialEquations.jl to automatically take advantage of your code, by passing in a Dual number (which behaves like a float, i.e. has the same kinds of methods like ^ and * etc. implemented, but isn’t just a wrapper around Float64) to do automatic differentiation, enable execution tracing and a number of other things.
May I ask what kind of OOP you’re referring to? If you’re thinking Java-style where you’re inheriting not only behavior but also struct fields, you’re going to have a bad time trying to replicate it in julia. Method level type assertions on arguments should in my opinion only be used to distinguish different implementations in dispatch, not for limiting incoming arguments (there is no performance difference between a typed and untyped argument in a method, except in very, very rare cases you’re unlikely to run into).