-
Notifications
You must be signed in to change notification settings - Fork 19
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
add more functionlenses #46
Conversation
Nice, thanks a lot @aplavin .
I think it makes sense to ask maintainers of InverseFunctions.jl if they are willing to include the inverses you defined here. If so I would be happy to add InverseFunctions.jl as a dependency.
One can do |
If we add those inverses to InverseFunctions, could you maybe even have a default definition Accessors.set(obj, lens, val) = InverseFunctions.inverse(lens)(val) in Accessors, even? |
With JuliaMath/InverseFunctions.jl#10 we could define things like
This would give us behavior like julia> right_inverse(@optic _.a.b.c)(42)
(a = (b = (c = 42,),),) CC @devmotion |
While possible, this would make error messages less helpful. Eg, this won't trigger anymore: Lines 163 to 166 in 5854cda
Can we somehow keep that error message without explicitly constraining
to a specific list (Union) of lens types? |
Do you have a real(ish) example of how this could be useful? |
I think it's elegant, having a constructor as the right-inverse of an optic. As for real world use, one could merge the inverses of projectors/optics to get the original value or so? |
That is an interesting idea and it is nice to automatically have Accessors support for invertible functions. OTOH nice error messages are also important and invertible lenses are the exception rather than the rule. If I have to choose one or the other I am in favor of the nice errors. |
I think I don't fully get what you mean. Can you make a very explicit example? |
Being invertible is much stronger than being just a lens. I don't know if there is an optics name for this. It would be nice to reflect this in the type system by a |
I thought
Given right_inverse(f::ComposedFunction) = Base.ComposedFunction(right_inverse(f.inner), right_inverse(f.outer))
using Accessors
right_inverse(::Accessors.PropertyLens{field}) where field = x -> NamedTuple{(field,)}((x,)) we could define using ChainRulesCore
function ChainRulesCore.rrule(f::Accessors.PropertyLens, x)
lens_pullback(Δy) = NoTangent(), (Δx = right_inverse(f)(Δy); Tangent{typeof(x),typeof(Δx)}(Δx))
f(x), lens_pullback
end and get (as expected) julia> using Zygote
julia> Zygote.gradient(@optic(_.b.c), (a = 1, b = (c = 2, d = 3), e = 4))
((a = nothing, b = (c = 1.0, d = nothing), e = nothing),)
|
True! I find the connection intriguing, though, in that |
With the following trick, any function has a right inverse: left(x) = 0 # what is the preimage of `1`? Does not look like this function has a right inverse
struct right
x
end
left(o::right) = o.x
@assert left(right(1)) === 1 # yeah we found a right inverse But this right inverse is not very useful. Because we changed the domains of our functions. y = left(x)
x2 = right_inverse(y) I expect So I am not sure how useful the proposed inverse for property lens is. More examples might convince me. |
I think that's a bit different, though, since in your example you specialize
Well, I would argue that a struct and a
I can't claim to have a "must have this feature" example right now, and we don't have a |
Will merge soon if no objections |
add more functionlenses
Motivation for these lenses, as I mentioned in another recent PR #45:
Suppose there is a function that modifies the value located by an optic:
Then, one can call it like
f([(a=1,), (a=2,), ...], @optic _.a)
.This PR goes further.
Want to operate on logarithms?
f([...], @optic log(_.a))
.Scaled values?
f([...], @optic _.a * 1e10)
.Sigmoid transformation?
f([...], @optic 1/(1 + exp(-_.a)))
Only consider and modify angles of complex values?
f([...], @optic angle(_.a))
and so on.
I should note that these
set
definitions are basically inverses, and it feels somewhat weird to define inverses for Base functions here. I don't have a better solution myself, so opening this for discussion.https://github.com/JuliaMath/InverseFunctions.jl exists though...
Another unfortunate shortcoming is that
set
works out of the box with@optic 1/(1 + exp(-_.a))
, but not withf(x) = 1 / (1 + exp(-x)); @optic f(_.a)
. Not sure if this is possible to overcome at all.As checked in the testsuite, all those
set
methods infer correctly, even in composition. For larger compositions the compiler will give up at some point, but for now the main usecase is short inline functions anyways.