Skip to content

Conversation

@j-fu
Copy link
Member

@j-fu j-fu commented Nov 21, 2025

Thanks, Philip, let's discuss this.

@j-fu j-fu marked this pull request as draft November 21, 2025 06:23
@j-fu
Copy link
Member Author

j-fu commented Nov 21, 2025

I did something similar here:

https://github.com/WIAS-PDELib/ExtendableGrids.jl/tree/jpt/extension-error-proof-of-concept

May be this is even sufficient. Let us see what the others say.

@jpthiele
Copy link
Contributor

I have now fiddled around with custom errors.
In a temporary project containing a *.msh file I dev'ed ExtendableGrids and add'ed Gmsh.
Then used the following code to test by hand

using ExtendableGrids
simplexgrid_from_gmsh("testgrid.msh") // -> throws ExtensionLoadError telling us that we need to install and load Gmsh, good!
using Gmsh
simplexgrid_from_gmsh("testgrid.msh") // -> works as expected
simplexgrid_from_gmsh(5) // -> throws ExtensionMethodError telling us that this combination is not available and which candidates are available instead.

@jpthiele
Copy link
Contributor

Mid term the plan would be to put this in its own package since it's useful in many places.
The one thing I still need to figure out is how to use varargs in multiple places.
Currently everything works for functions like foo(x;kwargs...) but it should also work for foo(x,y;kwargs...), etc.

  • prepare_extension_function(:foo, [:Gmsh],:x,:y) should produce foo(x,y;kwargs...)
  • ExtensionMethodError should also work for functions with more than one argument (possibly easier)
    Finally it would also be nice to specify kwargs for a function but that might be overkill

@jpthiele
Copy link
Contributor

@pjaap @j-fu I have now figured out how to do multiple arguments as well but would have restructure a lot to test it here as well. But I have tested it in a small separate file.

I have also build this small macro:

macro funfun(ex)
  :( function $(ex.args[1])($(ex.args[2:end]...)) end)
end

Then, calling @funfun foo(x,y) creates the function foo(x,y) which so far does nothing.
But I think I can combine what I put into the prepare_extension_function into a macro so we could instead write @extension_function simplexgrid(filename) [:Gmsh] or similar to produce the function taking a filename of Any type that either throws the ExtensionLoad Error if Gmsh is not loaded or the ExtensionMethodError if it is used with a different type than in the extension

Copy link
Member

@pjaap pjaap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting work :)

function prepare_extension_function(fname::Symbol, weakdeps::Vector{Symbol}, args::Vector{Symbol})
expr = quote
function $fname($(args...); kwargs...)
if mapreduce(x -> isdefined(Main, x) && isa(getfield(Main,x),Module), &, $weakdeps)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you want to reduce by the operator && and not the binary &? I think they behave the same, but && is clearer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would but reduce does not like that operator for some reason

end

function simplexgrid_from_gmsh end
prepare_extension_function(:simplexgrid_from_gmsh, [:Gmsh], [:filename])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to enforce key word arguments here:

prepare_extension_function(:simplexgrid_from_gmsh, weakdeps = [:Gmsh], args = [:filename])

@pjaap
Copy link
Member

pjaap commented Nov 27, 2025

Then, calling @funfun foo(x,y) creates the function foo(x,y) which so far does nothing.
But I think I can combine what I put into the prepare_extension_function into a macro so we could instead write @extension_function simplexgrid(filename) [:Gmsh] or similar to produce the function taking a filename of Any type that either throws the ExtensionLoad Error if Gmsh is not loaded or the ExtensionMethodError if it is used with a different type than in the extension

Do not get your hands too dirty with macros 😉

Does this work if the extension function is type-annotated?

@jpthiele
Copy link
Contributor

Then, calling @funfun foo(x,y) creates the function foo(x,y) which so far does nothing.
But I think I can combine what I put into the prepare_extension_function into a macro so we could instead write @extension_function simplexgrid(filename) [:Gmsh] or similar to produce the function taking a filename of Any type that either throws the ExtensionLoad Error if Gmsh is not loaded or the ExtensionMethodError if it is used with a different type than in the extension

Do not get your hands too dirty with macros 😉

Does this work if the extension function is type-annotated?

The function in the extension actually has to be annotated otherwise you get a precompile error about overwriting the function in the extension.
So having no type annotation within the prepare function is by design such that the ExtensionMethodError can catch any calls with wrong types

@j-fu
Copy link
Member Author

j-fu commented Nov 30, 2025

Ah I messed up the link in my previous post.
In ExampleJuggler.jl I have this:
https://github.com/j-fu/ExampleJuggler.jl/blob/283870bf08247b1a9492ea782dd95e71957ea1b4/src/plutoext.jl#L21

I am not sure if we need more of this. It can certainly make sense to have an extra error type though.
As for the generalization and the argument list - how would this work if the extension adds several method, or several extensions add methods ?

@jpthiele
Copy link
Contributor

In case of more methods Julia is able to find all defined methods using methods(fn) and I filter out the calling method from that list to return all others. simplexgrid_from_gmsh has two, with filename and mod and both are then shown.
Here is the output of both errors in action:
Extensionerroroutput

@jpthiele
Copy link
Contributor

As for multiple extensions:
I had not thought about that yet but we could add the list of extensions as a vector of vectors and change the ExtensionLoadError output accordingly if the length of the vector is more than 1.
An extension which needs both Gmsh and Pluto would then call
prepare_extension_function(:foo,[[:Gmsh,:Pluto]],[:x]) (seeing that I should really switch args and weakdeps...)
while two extensions needing either Gmsh or Pluto would be called as
prepare_extension_function(:foo,[[:Gmsh],[:Pluto]],[:x])

And to increase visual difference I would add a convenience function that takes a single vector and calls the vector of vectors function internally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants