All Projects → softwaremill → Quicklens

softwaremill / Quicklens

Licence: apache-2.0
Modify deeply nested case class fields

Programming Languages

scala
5932 projects

Projects that are alternatives of or similar to Quicklens

Python Lenses
A python lens library for manipulating deeply nested immutable structures
Stars: ✭ 179 (-72.07%)
Mutual labels:  lenses, functional-programming
Lambda
Functional patterns for Java
Stars: ✭ 737 (+14.98%)
Mutual labels:  lenses, functional-programming
Monocle Ts
Functional optics: a (partial) porting of Scala monocle
Stars: ✭ 657 (+2.5%)
Mutual labels:  lenses, functional-programming
Language Ext
C# functional language extensions - a base class library for functional programming
Stars: ✭ 3,964 (+518.41%)
Mutual labels:  lenses, functional-programming
Fsharpplus
Extensions for F#
Stars: ✭ 589 (-8.11%)
Mutual labels:  lenses
Learn Fp
learn-by-doing course/tutorial for functional programming on scala
Stars: ✭ 548 (-14.51%)
Mutual labels:  functional-programming
Moses
Utility library for functional programming in Lua
Stars: ✭ 541 (-15.6%)
Mutual labels:  functional-programming
Macroid
A modular functional UI language for Android
Stars: ✭ 537 (-16.22%)
Mutual labels:  functional-programming
Better Monadic For
Desugaring scala `for` without implicit `withFilter`s
Stars: ✭ 622 (-2.96%)
Mutual labels:  functional-programming
Mybatis Dynamic Sql
SQL DSL (Domain Specific Language) for Kotlin and Java. Supports rendering for MyBatis or Spring JDBC Templates
Stars: ✭ 614 (-4.21%)
Mutual labels:  functional-programming
Bash Oo Framework
Bash Infinity is a modern standard library / framework / boilerplate for Bash
Stars: ✭ 5,247 (+718.56%)
Mutual labels:  functional-programming
Ramda Adjunct
Ramda Adjunct is the most popular and most comprehensive set of functional utilities for use with Ramda, providing a variety of useful, well tested functions with excellent documentation.
Stars: ✭ 550 (-14.2%)
Mutual labels:  functional-programming
Funfix
Functional Programming Library for JavaScript, TypeScript and Flow ✨⚡️
Stars: ✭ 596 (-7.02%)
Mutual labels:  functional-programming
Pampy.js
Pampy.js: Pattern Matching for JavaScript
Stars: ✭ 544 (-15.13%)
Mutual labels:  functional-programming
Glance
A visual Haskell
Stars: ✭ 620 (-3.28%)
Mutual labels:  functional-programming
Bow
🏹 Bow is a cross-platform library for Typed Functional Programming in Swift
Stars: ✭ 538 (-16.07%)
Mutual labels:  functional-programming
Functional Programming Learning Path
A Learning Path for Functional Programming
Stars: ✭ 582 (-9.2%)
Mutual labels:  functional-programming
Focal
Program user interfaces the FRP way.
Stars: ✭ 613 (-4.37%)
Mutual labels:  lenses
Caliban
Functional GraphQL library for Scala
Stars: ✭ 581 (-9.36%)
Mutual labels:  functional-programming
React Best Practices
A comprehensive reference guide to kickstart your React architecting career!
Stars: ✭ 566 (-11.7%)
Mutual labels:  functional-programming

Quicklens

Maven Central Build Status

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 Seqs, Options and Mapss - 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 elements in an option/sequence/map using .at:

person.modify(_.addresses.at(2).street.at.name).using(_.toUpperCase)

Similarly to .each, .at modifies only the element at the given index/key. If there's no element at that index, an IndexOutOfBoundsException is thrown. In the above example, .at(2) selects an element in addresses: List[Address] and .at selects the lone possible element in street: Option[Street]. If street is None, a NoSuchElementException is thrown.

.at works for map keys as well:

case class Property(value: String)

case class PersonWithProps(name: String, props: Map[String, Property])

val personWithProps = PersonWithProps(
  "Joe",
  Map("Role" -> Property("Programmmer"), "Age" -> Property("45"))
)

personWithProps.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 specific elements in an option/sequence/map using .index:

person.modify(_.addresses.index(2).street.index.name).using(_.toUpperCase)

Similarly to .at, .index modifies only the element at the given index/key. If there's no element at that index, no modification is made. In the above example, .index(2) selects an element in addresses: List[Address] and .index selects the lone possible element in street: Option[Street]. If street is None, no modification is made.

.index works for map keys as well:

case class Property(value: String)

case class PersonWithProps(name: String, props: Map[String, Property])

val personWithProps = PersonWithProps(
  "Joe",
  Map("Role" -> Property("Programmmer"), "Age" -> Property("45"))
)

personWithProps.modify(_.props.index("Age").value).setTo("45")

Similarly to .at, .index modifies only the element with the given key. If there's no such element, no modification is made.

Modify specific elements in an option or map with a fallback using .atOrElse:

personWithProps.modify(_.props.atOrElse("NumReports", Property("0")).value).setTo("5")

If props contains an entry for "NumReports", then .atOrElse behaves the same as .at and the second parameter is never evaluated. If there is no entry, then .atOrElse will make one using the second parameter and perform subsequent modifications on the newly instantiated default.

For Options, .atOrElse takes no arguments and acts similarly.

person.modify(_.addresses.at(2).street.atOrElse(Street("main street")).name).using(_.toUpperCase)

.atOrElse is currently not available for sequences because quicklens might need to insert many elements in the list in order to ensure that one is available at a particular position, and it's not clear that providing one default for all keys is the right behavior.

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: Seq[Int]) extends Animal

case class Zoo(animals: Seq[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.6.1"

Also available for Scala.js and Scala Native!

Commercial Support

We offer commercial support for Quicklens and related technologies, as well as development services. Contact us to learn more about our offer!

Copyright

Copyright (C) 2015-2019 SoftwareMill https://softwaremill.com.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].