Hey, I wanted to look more into using conditional dependencies, and am looking for insight from both developers who have done this successfully and feedback from users on what is more intuitive.
Just so we're all on the same page, a conditional dependency is a package which your package does not need in most cases. One big example is Plots.jl which allows you to use different backends (PyPlot.jl, GR.jl, etc.) for your plotting. As you can see here <https://github.com/tbreloff/Plots.jl/blob/master/src/backends/gr.jl> there is an eval code which will import something like the GR backend during some initialization step the first time GR is needed. Another way this is used is somewhere like JuMP + friends. In this ecosystem, where (specifically for MathProgBase.jl) solvers may be imported by users <http://mathprogbasejl.readthedocs.io/en/latest/solvers.html#choosing-solvers> and then the appropriate functions are passed in. From my understanding, this means that the definitions of the abstract types must be in a different package, which the semi-dependencies must import and extend? It seems that there is another system here <https://github.com/JuliaOpt/MathProgBase.jl/blob/master/src/defaultsolvers.jl> where some things are actually imported to MathProgBase? (I am wondering if someone who knows this code could explain a little bit what's going on here). In DifferentialEquations.jl, I am using a third method for doing this via directly looking towards the Main module. An example is in the sde solvers <https://github.com/ChrisRackauckas/DifferentialEquations.jl/blob/master/src/sde/sdeSolvers.jl> where if a boolean is given and isdefined(Main,:Atom)==true, then Juno/Atom's progress bar is used by the solver by calling (atomLoaded && progressBar) ? Main.Atom.progress(t/T) : nothing #Use Atom's progressbar if loaded every so often in the loop. This method for a conditional import requires, like in the MathProgBase.jl implementation with passing the solver, that the user explicitly put "using Atom" at the top of the code for it to work. Luckily Juno does this automatically so this ends up seemless. I am wondering what the pros/cons of these different methods for implementing conditional dependencies are in order to start "ballooning" my feature list, while not tagging on a large number of dependencies. There are two different places where I see new conditional dependencies being added in my code. One place is with "new solver methods". As you can see from something like the ODE solvers <https://github.com/ChrisRackauckas/DifferentialEquations.jl/blob/master/src/ode/odeSolvers.jl>, I allow a user to choose a large number of different ways of solving the method. I plan on, in the very near future, making it conditional -> loop instead of having the conditional in the loop (that was just because of how I first wrote it!), and once that's the case, the next goal is to add other ODE solvers from other people's packages, i.e. make a new part of the conditional "if alg==:ode23" call ODE.jl's ode23 on the ODE. In this case, the dependency may be pretty obvious to the user, maybe it's better to require that the user do "using ODE" in their code and make it explicit? The Plots.jl method would have it silently import ODE and throw a good error message if it doesn't exist. What are the pros/cons here? However, a very different case for adding new conditional dependencies is something like seen in the implementation of the trapezoidal method. The loop for the solver for this is simply: elseif alg=="Trapezoid" uOld = copy(u) u = vec(u) nlres = nlsolve((u,resid)->rhs(u,resid,uOld,t,Δt),u) u = reshape(nlres.zero,sizeu...) i.e. it just solves an appropriate implicit equation using NLsolve.jl. Because of this (and a few other solvers which use an implicit solver), DifferentialEquations.jl currently depends on NLsolve, though in many (maybe most) cases the user won't need it. A simple implementation by using Main.nlsolve would require the user to have "using NLsolve" at the top of their code which may scare a lot of people away (thinking there is a bug instead of a missing package), so I think a Plots.jl implementation of silently importing the package when needed and throwing an error if it doesn't exist would be most suitable? I am quite interested in what people think about this. I wonder if anything is really becoming "standard" for implementing these kinds of conditional dependencies and will follow whatever standard makes sense to both users and other developers (Stefan mentions this as something that may be coming later in Julia, so maybe we should starting thinking about the best way to do this now! <https://www.youtube.com/watch?v=5gXMpbY1kJY>). [And sorry for the current state of the DifferentialEquations.jl code base, it was written to work first, and I know it needs some cleaning/re-naming, and slight re-structuring of the main loop to put the conditionals out...]
