quicklens
quicklens copied to clipboard
implement extractors
As we can't use:
scala> def extractor(p: Person) = p.address.street.name
extractor: (p: Person)String
scala> val p2 = person.modify(extractor).using(_.toUpperCase)
<console>:21: error: Path must have shape: _.field1.field2.each.field3.(...)
as it's hard to analyze such complex AST, it would be great to have something like:
person.modify(_.address.street.name).get
Which will return a List
of values. This will allow to reuse a path for both read and write.
My practical need is to actually check a path at one instance of case class, but modify the same path in another instance of the same case class:
val accessor = (_: Person).modify(_.address.street.name) //"access" alias for "modify" would be also great
if(accessor(person1).get == "Functional Road") accessor(person2).setTo("Imperative Road")
Currently, I've got to hack it with vars (or alternate the model), which is not very functional...
def getter[T, U](pm: T => PathModify[T, U])(in: T): List[U] = {
var values: List[U] = Nil //list is just in case of "each"
pm(in).using{x =>
values ++= x
x
}
values
}
so in fact you want to create lenses, right? :)
You could have: (_: Person).lens(_.x.y.z).get
, .setTo
, .modifyUsing
, what do you think?
But maybe if you need lenses, https://github.com/julien-truffaut/Monocle already has what you need?
:+1: for (_: Person).lens(_.x.y.z).get
, .setTo
, .modifyUsing
I also have a use case where that would be super convenient. Of course I could use Monocle, but I prefer the terse syntax of quicklens
.
There's one major problem here unfortunately: when you are using e.g. .each
unwrapping. Then you wouldn't be able to get a single value (as there might be 0-many values).
A partial solution could be to constrain .lens
no to allow .each
unwrapping of lists and such (during compile-time), but then wouldn't the (now quite simple) API become complex?
FWIW, in Monocle the things that can extract/modify several values at once (but not get a single value) are called Traversals.
I think it's possible to come up with both a simple and powerful API here, but that might require making the implementation more complex.
More concretely, I would suggest that get
return a list if .each
was used at some point in the chain. After all, the return type does not need to be static since the API makes use of macros.
@jedesah I would be against using whitebox macros, for two main reasons: they are not IDE-friendly (you don't know the return type until you actually run the compiler), and they won't be supported in the new scala-meta based macros (while you should be able to implement quicklens in its current form using scala meta)
I'm using such code right now to have .get
method
class QuckLensExt[T,U](private val pm: PathModify[T, U]) extends AnyVal {
def get = {
var values: List[U] = Nil
pm.using { x =>
values ::= x
x
}
values
}
}
even if introducing get
is not an option it would be nice to have cheaper way of implementing this hack. For example pm.usingPartial { case x if {values ::= x; false} => x }
just to not copy object unnecessarily.