Skip to content
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

API to query if something is a lens/optic #50

Open
tpapp opened this issue May 10, 2022 · 6 comments
Open

API to query if something is a lens/optic #50

tpapp opened this issue May 10, 2022 · 6 comments

Comments

@tpapp
Copy link
Contributor

tpapp commented May 10, 2022

It would be very useful to have a function that can determine if an object obeys the lens API. Ideally with traits. Eg something like

julia> is_optic(@optic _.a)
Val{true}()

(After discussion, I am of course happy to make a PR).

@jw3126
Copy link
Member

jw3126 commented May 10, 2022

This is a tricky design question. It will need multiple iterations before we get it right, but let'stry. Some thoughts:

  • We should probably start with islens, since lens has an official definition (satisfies lens laws). We don't really have a definition of what exactly an optic is
  • Currently it is super lightweight to define a lens. Just take a callable and overload Accessors.set(obj, lens, val). With this proposal, one needs to make sure that islens also gets overloaded. Maybe we should add a public test_lens_interface(obj, lens, val) function that kindly suggests missing overloads and other API violations.
  • I would be interested in your use cases? I wanted this myself a couple of times, but always found ways around it in the past.
  • I like the idea returning Val(::Bool), but it does not seem so popular in the ecosystem?

@tpapp
Copy link
Contributor Author

tpapp commented May 10, 2022

Let's start with the use case: a function can take a lens, or a black box closure that (object, f) -> ... that uses f to change something (specified by the closure) in object and returns a copy. Eg

struct Foo
   a
   b
end

with either _.a (the lens) or (x, f) -> Foo(f(x.a), x.b)). The lens would be applied with modify, the closure directly. So I would need to know what I am dealing with.

Of course I would prefer the lens, but there are corner cases when just throwing a closure at it is easier for the user.

Regarding your suggestions:

  1. yes, exposing if something if a lens is better,
  2. a test interface would be great,
  3. (described above),
  4. we could use singleton types but I think that is overkill, so I would just go with Val(::Bool).

@aplavin
Copy link
Member

aplavin commented May 11, 2022

I think this use case can best be solved with a function like objf_closure_to_modifiable_optic(closure). It should return a struct that wraps the closure and has Accessors.modify() defined in terms of it. Then the user would either call your_func(lens), or your_func(objf_closure_to_modifiable_optic(closure)).

Requiring all lenses to define islens is highly breaking: all current user-defined lenses won't be "officially" considered lenses anymore. And I can hardly see many users actually define islens for their lenses, if it's not required.

@jw3126
Copy link
Member

jw3126 commented May 11, 2022

I think @aplavin makes a good point. We need more use cases to justify adding islens.

@tpapp
Copy link
Contributor Author

tpapp commented May 11, 2022

@aplavin: I agree that it can be implemented with that wrapper, but it depends on the context whether one wants to make that part of an interface. The only issue is user convenience.

@jw3126: Yes, I understand that it is a breaking change, and there is no need to rush so let's leave this issue open to collect use cases. That said, a lot of Julia APIs have a function to query if something supports that API, eg Tables.istable.

@aplavin
Copy link
Member

aplavin commented May 11, 2022

but it depends on the context whether one wants to make that part of an interface. The only issue is user convenience.

My suggestion was precisely to make it part of the user interface. In simple cases, users pass lenses: your_func(@optic _.a), your_func(myfunc)...
In cases that cannot easily be described by lenses, they pass the anonymous function. It makes sense to highlight these complex (and presumably rare) cases: your_func(make_lens_from_closure(their_closure))).

That said, a lot of Julia APIs have a function to query if something supports that API, eg Tables.istable.

Implementing a Table from scratch is much more involved than a lens, so an extra method definition doesn't noticeably affect it. Meanwhile, a simple lens definition is just:

name(x::Obj) = x.name
set(x::Obj, ::typeof(name), v) = @set x.name = v

Defining another method (islens) would increase the "overhead" for making the property settable two-fold.

If one doesn't care much about corner cases, it should be possible to craft is_settable_lens and is_modifyable_lens functions with Base.hasmethod...
Or maybe your function could just check applicable(arg, yourobject, yourf)? If true, then the user likely passed that two-argument closure function.

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

No branches or pull requests

3 participants