-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Triggering extensions from within package #49990
Comments
Extensions work best when the user does not need to know about them. For example, in https://github.com/KristofferC/PGFPlotsX.jl/blob/master/ext/StatsBaseExt.jl there is no need to know that a StatsBase extension exists because it just extends existing functionality and there is no difference in behavior if StatsBase is a normal dependency or used as an extension in the package. Regarding the suggestion here, it feels equivalent to doing |
However, the user does need to know about the triggering package, which is "StatsBase" here... The current extension UX can sometimes be troublesome because you can't expect users to remember and understand what every triggering package does. For instance, ImageIO exists because we don't want our target users to remember the name of every image IO backends (JpegTurbo, PNGFiles, TiffImages, etc), but instead, we want them to only remember ImageIO and The UX that LazyModules provides is users know the API they care about (and not the triggering packages, unless we say triggering packages is a part of an API 😆), and the modules get loaded when the function gets first called. -- it's even lazier than the loading of extensions. To be honest, I don't know if the current extension design has the ability to support such lazy-loading usage. |
No, since the extension does not change any API they don't. The package just supports inputs from StatsBase types and if that support is implemented as an extension or a normal dependency it makes no visible difference to users.
FWIW, I find the FileIO, ImageIO API stuff very confusing. Trying to use it: julia> using ImageIO
julia> ImageIO.load("/home/kc/Pictures/pro.png")
ERROR: MethodError: no method matching load(::String)
Closest candidates are:
load(::FileIO.Stream{FileIO.DataFormat{:PNG}}; kwargs...)
@ ImageIO ~/.julia/packages/ImageIO/xMHN9/src/ImageIO.jl:64 Hm okay, why would julia> using FileIO
julia>: FileIO.load("/home/kc/Pictures/pro.png")
833×1064 Array{RGBA{N0f8},2} with eltype ColorTypes.RGBA{FixedPointNumbers.N0f8}:
RGBA{N0f8}(1.0,1.0,1.0,1.0) RGBA{N0f8}(1.0,1.0,1.0,1.0) … RGBA{N0f8}(1.0,1.0,1.0,1.0) So I add a package (ImageIO) but I don't load it or use it? Very strange. But let's look at what Let's try with just FileIO this time: julia> FileIO.load("/home/kc/Pictures/pro.png")
Errors encountered while load File{DataFormat{:PNG}, String}("/home/kc/Pictures/pro.png").
All errors:
===========================================
ArgumentError: Package ImageIO [82e4d734-157c-48bb-816b-45c225c6df19] is required but does not seem to be installed:
- Run `Pkg.instantiate()` to install all recorded dependencies.
===========================================
ArgumentError: Package ImageMagick [6218d12a-5da1-5696-b52f-db25d2ecc6d1] is required but does not seem to be installed:
- Run `Pkg.instantiate()` to install all recorded dependencies.
===========================================
Fatal error:
ERROR: ArgumentError: Package ImageIO [82e4d734-157c-48bb-816b-45c225c6df19] is required but does not seem to be installed:
- Run `Pkg.instantiate()` to install all recorded dependencies.
Stacktrace:
[1] _require(pkg::Base.PkgId, env::Nothing)
@ Base ./loading.jl:1739
...
[Two 10+ frame long stacktraces here] Okay, so I do need to know the names of the packages to be able to use FileIO, it's just that ImageIO bundled a bunch of these into a "mega loader". So the only convenience FileIO brings is that the packages gets loaded automatically, but you still need to add them. Imo, it is better to leave it to the users to explicitly load the package. For example, in FileIO, there could be an extension to ImageMagick that provides e.g. a png loader. If that is not loaded, you get an error message saying that you need ImageMagick, the user adds that, loads it, and off they go. |
If you just want to load a png and you care about the package size, you can just use PNGFiles. In this case, you're not the target user of ImageIO 😆
As a developer, you do need to know and add it to your Whether ImageIO or ImageMagick is used to support the If one algorithm becomes one standalone package for latency reasons, imagine that users have to remember all the sorting algorithm names to start using using RadixSort
using QuickSort
ssort(x) # a smart sort function that internally delegates to the most efficient implementation, if available Revisit JuliaLang/Pkg.jl#2005, it seems that we don't have a clear standard way to really let |
I think I wasn't clear in my original post. Extensions are great for extend a user interface to the types of an external package (such as your example of defining a custom What am I more asking about is how one could exploit package extensions for internal use within a package. Here's an example to help clarify what I mean. Say that my package includes an optimization routine of a user-defined function, using Optim.jl. Optim.jl lets you pass a function But, I don't want the user to have to write The optimal choice depends on various factors; one being on the number of input parameters. I might choose to use ReverseDiff.jl for In code, this would look like: function optimize_f(f, params)
n = length(params)
g! = if n > 10
@eval import ReverseDiff
# ... create g! here
else
@eval import ForwardDiff
# ... create g! here
end
optim(f, g!, params, ...)
end This example code is not robust, sadly, due to world age issues. I also encountered world age problems when I tried to use Does this help explain what I mean? |
@KristofferC I tried again with Here's a summary (full example here). In my package file generate_diff_operators(args...) = error("`Zygote` not loaded.")
function create_diffs(binary_operators, unary_operators)
Base.require(@__MODULE__, :Zygote)
generate_diff_operators(binary_operators, unary_operators)
end then, in module DynamicExpressionsZygoteExt
import Zygote: gradient
import DynamicExpressions: generate_diff_operators
function generate_diff_operators(
binary_operators::Vector{Function}, unary_operators::Vector{Function}
)
...
end
end I would hope that running Would that be possible for the trigger code to check? If that works, then I think this would already be possible with extensions. |
Update: It seems like 1.9 backport #49680 actually fixes the above issue! Because of #49701, extensions now will load after internal loading of a module, like is done here. So after that patch, running The one sharp edge is that you need to still call @KristofferC one bug I noticed is that using an extension like this requires you to simultaneously define the package in both
However, if I define it in both I can raise this as a bug separately. |
Moved to bug report in #50028 |
Right now when a user wants to use an extension, they load the dependency themselves, and this triggers the extension. However, this requires the user to know when for what part of the code need to use it.
I think it would be very useful for downstream users of a package if there was a way for a package maintainer to automatically trigger a particular extension from within their package.
For example, say I want to use Zygote.jl inside my package, but only for some rarely-used branch of my code. Due to the 300ms load time, I don't want to always load it. I could set up an extension, but it would require all downstream users to load Zygote.jl manually. But downstream users might not even know where Zygote is being used inside the package, or if it is being used at all.
So, it would be great if there could be a way to manually trigger an extension to load. Something like
Base.load_extension(@__MODULE__, :ZygoteExt)
, similar toget_extension
.@KristofferC wdyt?
Related discussion: johnnychen94/LazyModules.jl#10
Edit: here's a clarifying post: #49990 (comment)
The text was updated successfully, but these errors were encountered: