All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scalapb.lenses.Lenses.scala Maven / Gradle / Ivy

The newest version!
package scalapb.lenses

trait Lens[Container, A] extends Serializable {
  self =>

  /** get knows how to extract some field of type `A` from a container */
  def get(c: Container): A

  /** Represents an assignment operator.
    *
    * Given a value of type A, sets knows how to transform a container such that `a` is assigned to
    * the field.
    *
    * We must have get(set(a)(c)) == a
    */
  def set(a: A): Mutation[Container]

  /** alias to set */
  def :=(a: A) = set(a)

  /** Optional assignment.
    *
    * Given a `Some[A]`, assign the `Some`'s value to the field. Given `None`, the container is
    * unchanged.
    */
  def setIfDefined(aOpt: Option[A]): Mutation[Container] =
    c => aOpt.fold(c)(set(_)(c))

  /** Represent an update operator (like x.y += 1 ) */
  def modify(f: A => A): Mutation[Container] = c => set(f(get(c)))(c)

  /** Composes two lenses, this enables nesting.
    *
    * If our field of type A has a sub-field of type B, then given a lens for it (other: Lens[A, B])
    * we can create a single lens from Container to B.
    */
  def compose[B](other: Lens[A, B]): Lens[Container, B] = new Lens[Container, B] {
    def get(c: Container) = other.get(self.get(c))

    def set(b: B) = self.modify(other.set(b))
  }

  /** Given two lenses with the same origin, returns a new lens that can mutate both values
    * represented by both lenses through a tuple.
    */
  def zip[B](other: Lens[Container, B]): Lens[Container, (A, B)] = new Lens[Container, (A, B)] {
    def get(c: Container): (A, B)           = (self.get(c), other.get(c))
    def set(t: (A, B)): Mutation[Container] = self.set(t._1).andThen(other.set(t._2))
  }
}

object Lens extends CompatLensImplicits {
  /* Create a Lens from getter and setter. */
  def apply[Container, A](
      getter: Container => A
  )(setter: (Container, A) => Container): Lens[Container, A] = new Lens[Container, A] {
    def get(c: Container) = getter(c)

    def set(a: A): Mutation[Container] = setter(_, a)
  }

  /** This is the unit lens, with respect to the compose operation defined above. That is,
    * len.compose(unit) == len == unit.compose(len)
    *
    * More practically, you can view it as a len that mutates the entire object, instead of just a
    * field of it: get() gives the original object, and set() returns the assigned value, no matter
    * what the original value was.
    */
  def unit[U]: Lens[U, U] = Lens(identity[U])((_, v) => v)

  /** Implicit that adds some syntactic sugar if our lens watches an Option[_]. */
  implicit class OptLens[U, A](val lens: Lens[U, Option[A]]) extends AnyVal {
    def inplaceMap(f: Lens[A, A] => Mutation[A]) =
      lens.modify(opt =>
        opt.map { (m: A) =>
          val field: Lens[A, A] = Lens.unit[A]
          val p: Mutation[A]    = f(field)
          p(m)
        }
      )
  }

  /** Implicit that adds some syntactic sugar if our lens watches a Map[_, _]. */
  implicit class MapLens[U, A, B](val lens: Lens[U, Map[A, B]]) extends AnyVal {
    def apply(key: A): Lens[U, B] =
      lens.compose(Lens[Map[A, B], B](_.apply(key))((map, value) => map.updated(key, value)))

    def :+=(pair: (A, B)) = lens.modify(_ + pair)

    def :++=(item: Iterable[(A, B)]) = lens.modify(_ ++ item)

    def foreach(f: Lens[(A, B), (A, B)] => Mutation[(A, B)]): Mutation[U] =
      lens.modify(s =>
        s.map { (pair: (A, B)) =>
          val field: Lens[(A, B), (A, B)] = Lens.unit[(A, B)]
          val p: Mutation[(A, B)]         = f(field)
          p(pair)
        }
      )

    def foreachValue(f: Lens[B, B] => Mutation[B]): Mutation[U] =
      lens.modify(s =>
        s.map { case (k, m) =>
          val field: Lens[B, B] = Lens.unit[B]
          val p: Mutation[B]    = f(field)
          (k, p(m))
        }
      )

    def mapValues(f: B => B) = foreachValue(_.modify(f))
  }
}

class ObjectLens[U, Container](self: Lens[U, Container]) extends Lens[U, Container] {

  /** Creates a sub-lens */
  def field[A](lens: Lens[Container, A]): Lens[U, A] = self.compose(lens)

  /** Creates a sub-lens */
  def field[A](getter: Container => A)(setter: (Container, A) => Container): Lens[U, A] =
    field(Lens(getter)(setter))

  override def get(u: U) = self.get(u)

  override def set(c: Container) = self.set(c)

  def update(ms: (Lens[Container, Container] => Mutation[Container])*): Mutation[U] =
    u => set(ms.foldLeft[Container](get(u))((p, m) => m(Lens.unit[Container])(p)))(u)
}

// In Scala 3 source mode, message lenses extend this class to avoid an error of extending
// non-open class.
// TODO(thesamet): unify ObjectLens and MessageLens in next major release (#1581)
abstract class MessageLens[U, Container](self: Lens[U, Container]) extends ObjectLens(self)

trait Updatable[A] extends Any {
  self: A =>
  def update(ms: (Lens[A, A] => Mutation[A])*): A =
    ms.foldLeft[A](self)((p, m) => m(Lens.unit[A])(p))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy