Modify deeply nested fields in case classes:
import com.softwaremill.quicklens._
case class Street(name: String)
case class Address(street: Street)
case class Person(address: Address, age: Int)
val person = Person(Address(Street("1 Functional Rd.")), 35)
val p2 = person.modify(_.address.street.name).using(_.toUpperCase)
val p3 = person.modify(_.address.street.name).setTo("3 OO Ln.")
// or
val p4 = modify(person)(_.address.street.name).using(_.toUpperCase)
val p5 = modify(person)(_.address.street.name).setTo("3 OO Ln.")
Chain modifications:
person
.modify(_.address.street.name).using(_.toUpperCase)
.modify(_.age).using(_ - 1)
Modify conditionally:
person.modify(_.address.street.name).setToIfDefined(Some("3 00 Ln."))
person.modify(_.address.street.name).setToIf(shouldChangeAddress)("3 00 Ln.")
Modify several fields in one go:
import com.softwaremill.quicklens._
case class Person(firstName: String, middleName: Option[String], lastName: String)
val person = Person("john", Some("steve"), "smith")
person.modifyAll(_.firstName, _.middleName.each, _.lastName).using(_.capitalize)
Traverse options/lists/maps using .each:
import com.softwaremill.quicklens._
case class Street(name: String)
case class Address(street: Option[Street])
case class Person(addresses: List[Address])
val person = Person(List(
Address(Some(Street("1 Functional Rd."))),
Address(Some(Street("2 Imperative Dr.")))
))
val p2 = person.modify(_.addresses.each.street.each.name).using(_.toUpperCase)
.each
can only be used inside a modify
and "unwraps" the container (currently supports List
s, Option
s and
Maps
s - only values are unwrapped for maps).
You can add support for your own containers by providing an implicit QuicklensFunctor[C]
with the appropriate
C
type parameter.
Traverse selected elements using .eachWhere:
Similarly to .each
, you can use .eachWhere(p)
where p
is a predicate to modify only the elements which satisfy
the condition. All other elements remain unchanged.
def filterAddress: Address => Boolean = ???
person
.modify(_.addresses.eachWhere(filterAddress)
.street.eachWhere(_.name.startsWith("1")).name)
.using(_.toUpperCase)
Modify specific sequence elements using .at:
person.modify(_.addresses.at(2).street.each.name).using(_.toUpperCase)
Similarly to .each
, .at
modifies only the element at the given index. If there's no element at that index,
an IndexOutOfBoundsException
is thrown.
Modify specific map elements using .at:
case class Property(value: String)
case class Person(name: String, props: Map[String, Property])
val person = Person(
"Joe",
Map("Role" -> Property("Programmmer"), "Age" -> Property("45"))
)
person.modify(_.props.at("Age").value).setTo("45")
Similarly to .each
, .at
modifies only the element with the given key. If there's no such element,
an NoSuchElementException
is thrown.
Modify Either fields using .eachLeft and eachRight:
case class AuthContext(token: String)
case class AuthRequest(url: String)
case class Resource(auth: Either[AuthContext, AuthRequest])
val devResource = Resource(auth = Left(AuthContext("fake"))
val prodResource = devResource.modify(_.auth.eachLeft.token).setTo("real")
Modify fields when they are of a certain subtype:
trait Animal
case class Dog(age: Int) extends Animal
case class Cat(ages: List[Int]) extends Animal
case class Zoo(animals: List[Animal])
val zoo = Zoo(List(Dog(4), Cat(List(3, 12, 13))))
val olderZoo = zoo.modifyAll(
_.animals.each.when[Dog].age,
_.animals.each.when[Cat].ages.at(0)
).using(_ + 1)
This is also known as a prism, see e.g. here.
Re-usable modifications (lenses):
import com.softwaremill.quicklens._
val modifyStreetName = modify(_: Person)(_.address.street.name)
val p3 = modifyStreetName(person).using(_.toUpperCase)
val p4 = modifyStreetName(anotherPerson).using(_.toLowerCase)
//
val upperCaseStreetName = modify(_: Person)(_.address.street.name).using(_.toUpperCase)
val p5 = upperCaseStreetName(person)
Alternate syntax:
import com.softwaremill.quicklens._
val modifyStreetName = modify[Person](_.address.street.name)
val p3 = modifyStreetName.using(_.toUpperCase)(person)
val p4 = modifyStreetName.using(_.toLowerCase)(anotherPerson)
//
val upperCaseStreetName = modify[Person](_.address.street.name).using(_.toUpperCase)
val p5 = upperCaseStreetName(person)
Composing lenses:
import com.softwaremill.quicklens._
val modifyAddress = modify(_: Person)(_.address)
val modifyStreetName = modify(_: Address)(_.street.name)
val p6 = (modifyAddress andThenModify modifyStreetName)(person).using(_.toUpperCase)
or, with alternate syntax:
import com.softwaremill.quicklens._
val modifyAddress = modify[Person](_.address)
val modifyStreetName = modify[Address](_.street.name)
val p6 = (modifyAddress andThenModify modifyStreetName).using(_.toUpperCase)(person)
Modify nested sealed hierarchies:
Note: this feature is experimental and might not work due to compilation order issues. See https://issues.scala-lang.org/browse/SI-7046 for more details.
import com.softwaremill.quicklens._
sealed trait Pet { def name: String }
case class Fish(name: String) extends Pet
sealed trait LeggedPet extends Pet
case class Cat(name: String) extends LeggedPet
case class Dog(name: String) extends LeggedPet
val pets = List[Pet](
Fish("Finn"), Cat("Catia"), Dog("Douglas")
)
val juniorPets = pets.modify(_.each.name).using(_ + ", Jr.")
Similar to lenses (1, 2), but without the actual lens creation.
Read the blog for more info.
Available in Maven Central:
val quicklens = "com.softwaremill.quicklens" %% "quicklens" % "1.4.12"
Also available for Scala.js!