Giter Club home page Giter Club logo

zio-optics's Introduction

ZIO Optics

ZIO Optics is a library that makes it easy to modify parts of larger data structures based on a single representation of an optic as a combination of a getter and setter.

Development CI Badge Sonatype Releases Sonatype Snapshots javadoc ZIO Optics

Introduction

When we are working with immutable nested data structures, updating and reading operations could be tedious with lots of boilerplates. Optics is a functional programming construct that makes these operations more clear and readable.

Key features of ZIO Optics:

  • Unified Optic Data Type — All the data types like Lens, Prism, Optional, and so forth are type aliases for the core Optic data type.
  • Composability — We can compose optics to create more advanced ones.
  • Embracing the Tremendous Power of Concretion — Using concretion instead of unnecessary abstractions, makes the API more ergonomic and easy to use.
  • Integration with ZIO Data Types — It supports effectful and transactional optics that works with ZIO data structures like Ref and TMap.
  • Helpful Error Channel — Like ZIO, the Optics data type has error channels to include failure details.
  • Zero dependencies - No dependencies other than ZIO itself.
  • No unnecessary abstractions - Concrete representation makes it easy to learn.

The optic also handles the possibility of failure for us, failing with an OpticFailure that is a subtype of Throwable and contains a helpful error message if the key cannot be found.

ZIO Optics makes it easy to compose more complex optics from simpler ones, to define optics for your own data types, and to work with optics that use ZIO or STM effects.

Installation

In order to use this library, we need to add the following line in our build.sbt file:

libraryDependencies += "dev.zio" %% "zio-optics" % "0.2.0"

Example

ZIO Optics makes it easy to modify parts of larger data structures. For example, say we have a web application where users can vote on which of various topics they are interested in. We maintain our state of how many votes each topic has received as a Ref[Map[String, Int]].

import zio._

lazy val voteRef: Ref[Map[String, Int]] =
  ???

If we want to increment the number of votes for one of the topics here is what it would look like:

def incrementVotes(topic: String): Task[Unit] =
  voteRef.modify { voteMap =>
    voteMap.get(topic) match {
      case Some(votes) =>
        (ZIO.unit, voteMap + (topic -> (votes + 1)))
      case None        =>
        val message = s"voteMap $voteMap did not contain topic $topic"
        (ZIO.fail(new NoSuchElementException(message)), voteMap)
    }
  }.flatten

This is alright, but there is a lot of code here for a relatively simple operation of incrementing one of the keys. We have to get the value from the Ref, then get the value from the Map, and finally set the new value in the Map.

We also have to explicitly handle the possibility that the value is not in the map. And this is all for a relatively simple data structure!

Here is what this would look like with ZIO Optics.

import zio.optics._

def incrementVotes(topic: String): Task[Unit] =
  voteRef.key(topic).update(_ + 1)

The key optic "zooms in" on part of a larger structure, in this case transforming the Ref[Map[String, Int]] into a Ref that accesses the value at the specified key. We can then simply call the update operator on Ref to increment the value.

Let's try another example. We are going to update a nested data structure using ZIO Optics:

import zio.optics._

case class Developer(name: String, manager: Manager)
case class Manager(name: String, rating: Rating)
case class Rating(upvotes: Int, downvotes: Int)

val developerLens = Lens[Developer, Manager](
  get = developer => Right(developer.manager),
  set = manager => developer => Right(developer.copy(manager = manager))
)

val managerLens = Lens[Manager, Rating](
  get = manager => Right(manager.rating),
  set = rating => manager => Right(manager.copy(rating = rating))
)

val ratingLens = Lens[Rating, Int](
  get = rating => Right(rating.upvotes),
  set = upvotes => rating => Right(rating.copy(upvotes = upvotes))
)

// Composing lenses
val optic = developerLens >>> managerLens >>> ratingLens

val jane    = Developer("Jane", Manager("Steve", Rating(0, 0)))
val updated = optic.update(jane)(_ + 1)

println(updated)

Resources

  • Zymposium - Optics by Adam Fraser and Kit Langton (June 2021) — Optics are great tools for working with parts of larger data structures and come up in disguise in many places such as ZIO Test assertions.

Documentation

Learn more on the ZIO Optics homepage!

Contributing

For the general guidelines, see ZIO contributor's guide.

Code of Conduct

See the Code of Conduct

Support

Come chat with us on Badge-Discord.

License

License

zio-optics's People

Contributors

adamgfraser avatar ghostdogpr avatar khajavi avatar mijicd avatar renovate-bot avatar renovate[bot] avatar scala-steward avatar sideeffffect avatar vigoo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zio-optics's Issues

OpticFailure error message gets discarded

when:

val tmap: TMap[String, String] = ???
val effect = tmap.key("key does not exist").get(()).commit // does not preserve error message (if error channel type is Throwable)

expect stacktrace message starting with:
zio.optics.OpticFailureModule$OpticFailure: zio.stm.TMap@34449d84 did not satify hasKey(key does not exist)

but was:
zio.optics.OpticFailureModule$OpticFailure

full example:

import zio.stm._
import zio.optics.toptics._
object TestMain extends zio.App {

  override def run(args: List[String]): URIO[ZEnv,ExitCode] = {
    val stm = for {
      map <- TMap.empty[String, String]
      notFound <- map.key("key does not exist").get(())
    } yield notFound

    val effect: ZIO[Any,Throwable,String] = stm.commit


    val effectWithErrorMessage: ZIO[Any,Throwable,String] = stm.mapError(e => new RuntimeException(s"error message was ${e.message}")).commit

    effect.catchAll(e => ZIO.effect(e.printStackTrace())).exitCode
    //effectWithErrorMessage.catchAll(e => ZIO.effect(e.printStackTrace())).exitCode
  }

}

Upgrade to ZIO 2.0

ZIO 2.0 is at Milestone 4, with an RC expected in the next few weeks.
https://github.com/zio/zio/releases/tag/v2.0.0-M4

The API is nearly stable at this point, so any early migration work against this version should pay off towards the official 2.0 release.

The progress is being tracked here:
zio/zio#5470

The Stream Encoding work in progress is the only area where the API might still change before the RC.

We are actively working on a ScalaFix rule that will cover the bulk of the simple API changes:
https://github.com/zio/zio/blob/series/2.x/scalafix/rules/src/main/scala/fix/Zio2Upgrade.scala
We highly recommend starting with that, and then working through any remaining compilation errors :)

To assist with the rest of the migration, we have created this guide:
https://zio.dev/howto/migrate/zio-2.x-migration-guide/

If you would like assistance with the migration from myself or other ZIO contributors, please let us know!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.