Closed oschulz closed 2 years ago
An alternative would be to define something like
struct NoInverse{F}
f::F
end
(f::NoInverse)(x) = error("inverse of ", f.f, " is not defined")
inverse(f) = NoInverse(f)
Oh, this is neat!
It would be similar to the default NullParameters
in SciML (https://github.com/SciML/SciMLBase.jl/blob/9e5ba6f10a0d347d48ba4d2bae4a3c610a589bc1/src/problems/problem_utils.jl#L139-L141) which IIRC was Yingbo's idea at JuliaCon 2019.
Do we want inverse(f::NoInverse) = f.f
or inverse(f::NoInverse) = error(...)
?
See #14.
Still, how to check if a function is invertible? inverse(...) isa NoInverse
doesn't really solve this:
julia> inverse(sin ∘ inv)
inv ∘ NoInverse{typeof(sin)}(sin)
julia> inverse(sin ∘ inv) isa NoInverse
false
IMO that's a bug and generally one should check for NoInverse
. I think the example should just return NoInverse{typeof(sin \circ inv)}(sin \circ inv)
here. I guess this can be fixed by checking if the inverse of any of the functions in ComposedFunction
is NoInverse
.
I guess this can be fixed by checking if the inverse of any of the functions in ComposedFunction is NoInverse.
I agree, the result should be NoInverse{typeof{Composed{...}}
(so that it can still be inverted back to the original composed function).
This is a very elegant and nice mechanic!
Thanks @jw3126 !
I have a use case that needs to determine if a function has an inverse or not in a type-stable fashion (try/catch won't do). I think such a functionality would be useful in general.
I see two options:
a) Adding a
has_inverse(f)
function that returns a Singleton. This would be a breaking change, functions that supportinverse
would now be required to supporthas_inverse
as well.b) A default implementation
inverse(f) = nothing
(ormissing
?). This would be simpler (especially if we addleft_inverse
andright_inverse
as well later on) and non-breaking. On the other hand, code that depends on functions having an inverse would fail in a less obvious way, the root cause would be harder to track (users would get aMethodError: objects of type Missing are not callable
instead of aMethodError: no method matching inverse(::typeof(f))
). On the other hand, code that uses inverses could check if the inverse is notnothing
to ensure the exception points to the right place, e.g viasomething(inverse(f))
.An example for approach b) would be ChainRulesCore (
rrule(f, args...)
returnsnothing
by default).@devmotion what do you think?