-
Notifications
You must be signed in to change notification settings - Fork 450
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
Optics as extension on Companion #825
Conversation
…e-discoverability # Conflicts: # modules/core/arrow-annotations-processor/src/main/java/arrow/optics/BoundSetterGenerator.kt # modules/core/arrow-annotations-processor/src/main/java/arrow/optics/OpticsProcessor.kt # modules/docs/arrow-docs/src/main/kotlin/SyntaxHelper.kt
This will require declaring empty |
Codecov Report
@@ Coverage Diff @@
## master #825 +/- ##
============================================
+ Coverage 44.48% 44.55% +0.07%
+ Complexity 633 623 -10
============================================
Files 294 298 +4
Lines 7466 7492 +26
Branches 834 831 -3
============================================
+ Hits 3321 3338 +17
- Misses 3844 3851 +7
- Partials 301 303 +2
Continue to review full report at Codecov.
|
Yes it does and neither do I but we already require it for Concerning the syntax. I think That it looks like accessing a static field is fine for me as you could see it as a composition of functional references. @jereksel Any suggestions to improve this? We could also make them |
So let's do it another way - instead of val optic = (Employee.company compose
Company.address compose
Address.street compose
Street.name) Maybe we should go with something like val optic = (Employee.company compose
address compose
street compose
name) We could even go a step further val optic = Employee.company.address.street.name I'm fine with this additional companion as far as processor will throw readable error when one is missing. |
Snippet 1 & 2 are the same but with a different set of imports. I thought about snippet 3 but if we do that it would be a DSL on top of snippet 1 but not replace traditional composition. Just like we do with bound setter. |
I like the lenses in the companion more than functions on the package but the concern I have here is name collisions. |
@raulraja I didn't feel the need to add fixes for reasons explained below. I want to avoid confusion so I am open for suggestions.
That's actually the reason why I choose this option. In Haskell lenses are also referred to as This is not only done for lenses but also for optional values. i.e. @optics data class Person(val name: Name, val age: Age?, val address: Option<Address>) results in val Person.Companion.name: Lens<Person, Name> inline get()= ...
val Person.Companion.age: Optional<Person, Age> inline get()= ...
val Person.Companion.address: Optional<Person, Address> inline get()= ...
val Person.Companion.iso: Iso<Person, Tuple3<Name, Age?, Option<Address>>> inline get()= ... To be complete sealed class MaybeInt {
object Nothing: MaybeInt()
data class Just(val value: Int): MaybeInt()
}
val MaybeInt.Companion.nothing: Prism<MaybeInt, MaybeInt.Nothing> inline get()= ...
val MaybeInt.Companion.just: Prism<MaybeInt, MaybeInt.Nothing> inline get()=... |
@nomisRev alright, I'm sold 👍 |
I also like |
@raulraja I'd really love a DSL like However I think this is a much stronger feature than the current bounded syntax and am not sure if the additionally generated code is an issue given the power this offers. Also with this style syntax I personnaly don't feel the need anymore to support bounded syntax. Thoughts? @raulraja @jereksel @pakoito @JorgeCastilloPrz For example to make the following snippet compile @optics
data class Address(val city: String, val street: Street) {
companion object
}
@optics
data class Company(val name: String, val address: Address) {
companion object
}
@optics
data class Employee(val name: String, val company: Company?) {
companion object
}
val address: Optional<Employee, Address> = Employee.company.address We need to generate following code /** For Employee::company **/
inline val <S> Iso<S, Employee>.company: Optional<S, Company> get() = this + Employee.company
inline val <S> Lens<S, Employee>.company: Optional<S, Company> get() = this + Employee.company
inline val <S> Optional<S, Employee>.company: Optional<S, Company> get() = this + Employee.company
inline val <S> Prism<S, Employee>.company: Optional<S, Company> get() = this + Employee.company
inline val <S> Setter<S, Employee>.company: Setter<S, Company> get() = this + Employee.company
inline val <S> Traversal<S, Employee>.company: Traversal<S, Company> get() = this + Employee.company
inline val <S> Fold<S, Employee>.company: Fold<S, Company> get() = this + Employee.company
/** For Company::address **/
inline val <S> Iso<S, Company>.address: Lens<S, Address> get() = this + Company.address
inline val <S> Lens<S, Company>.address: Lens<S, Address> get() = this + Company.address
inline val <S> Optional<S, Company>.address: Optional<S, Address> get() = this + Company.address
inline val <S> Prism<S, Company>.address: Optional<S, Address> get() = this + Company.address
inline val <S> Getter<S, Company>.address: Getter<S, Address> get() = this + Company.address
inline val <S> Setter<S, Company>.address: Setter<S, Address> get() = this + Company.address
inline val <S> Traversal<S, Company>.address: Traversal<S, Address> get() = this + Company.address
inline val <S> Fold<S, Company>.address: Fold<S, Address> get() = this + Company.address |
@nomisRev I think that as long as those val's are not initialized until usage it would be fine regardless of the amount of code. If you think the new DSL would supersede the bounded syntax one then great!. We are in a privileged situation now that won't last much longer as we approach 1.0 and whatever makes the barrier of entry to lenses easier the better. |
@raulraja The to-generate-code is quite trivial so I should be able to add that over the weekend. Since the generated code are all Reason I think the new DSL supersedes the bounded syntax is that we're making actual Optics here instead of bounded instances so they live up to their true potential. Additionally, I think the bounded syntax is not worth the overhead
|
…lity' into simon-optics-improve-discoverability
937131b
to
f345a9a
Compare
@@ -0,0 +1,84 @@ | |||
package arrow.optics.dsl |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can this file be accessed, if not from the menu?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean DSL
docs? I updated menu, folder structure, and permalink.
Re-enabling grained control for DSL will be done in #848 |
@arrow-kt/maintainers ready for review :) |
import arrow.optics.optics | ||
|
||
@optics([OpticsTarget.ISO]) | ||
data class IsoCompanion(val field: String, val nullable: String?, val option: Option<String>) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Companion object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test model to test for a compiler error. https://github.com/arrow-kt/arrow/pull/825/files#diff-8bcf2e9e0361c1d797accab0aede2539R32
@@ -6,5 +6,6 @@ import arrow.optics.optics | |||
|
|||
@optics([OpticsTarget.ISO]) | |||
sealed class IsoSealed(val field: String, val nullable: String?, val option: Option<String>) { | |||
companion object {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for {}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome sauce! 👏 👏 This is huge for lenses adoption, This DSL makes it very easy to compose and modify deeply nested structures which is the main feature people want out of an optics lib.
@@ -73,14 +75,14 @@ import arrow.data.* | |||
val jane = Employee("Jane Doe", Company("Kategory", Address("Functional city", Street(42, "lambda street")))) | |||
val employees = Employees(listOf(john, jane).k()) | |||
|
|||
employees.setter().employees.every(ListK.each()).company.address.street.name.modify(String::capitalize) | |||
Employees.employees.every(ListK.each()).company.address.street.name.modify(employees, String::capitalize) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to provide ListK.each()
automatically based on the property type internally so the user can just do?
Employees.employees.every().company.address.street.name.modify(employees, String::capitalize)
Also if we detect types for which we now a wrapper exists for example List <-> ListK
, is it possible to preserve the user's type List
while providing internally a Each
instance for its wrapper type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@raulraja I tried to find an encoding for this but we need true typeclasses AFAIK.
We can do this only for one F
due to type erasure and I am unsure if we should favor List
/ListK
by adding
fun <T, A> Lens<T, ListK<A>>.every(): Traversal<T, A> = this compose ListK.traversal()
.
Because then we cannot do it for any other F
like
fun <T, A> Lens<T, SetK<A>>.every()
or fun <T, K, A> Lens<T, MapK<K, A>>.every()
* @param S the source of a [BoundSetter] | ||
* @param A the focus of a [BoundSetter] | ||
*/ | ||
class BoundSetter<S, A>(val value: S, val setter: Setter<S, A>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥 👏
Like it was done for generated Optics in #825 this PR moves the Optics of the std to the companion objects of the respective data types.
* Moves generated optics to Companion * Introduces a `.` syntax DSL for composing Optics
Like it was done for generated Optics in arrow-kt#825 this PR moves the Optics of the std to the companion objects of the respective data types.
Since other generators require
companion object
I propose the same requirement for optics generation. If so then we can generate optics as extension properties on the companion object instead of global functions. This will improve discoverability and place make for an easier to use API.will become
Thoughts?