|
| 1 | +package scalapb.lenses |
| 2 | + |
| 3 | +import scala.language.higherKinds |
| 4 | + |
| 5 | +trait Lens[Container, A] extends Any { |
| 6 | + self => |
| 7 | + /** get knows how to extract some field of type `A` from a container */ |
| 8 | + def get(c: Container): A |
| 9 | + |
| 10 | + /** Represents an assignment operator. |
| 11 | + * |
| 12 | + * Given a value of type A, sets knows how to transform a container such that `a` is |
| 13 | + * assigned to the field. |
| 14 | + * |
| 15 | + * We must have get(set(a)(c)) == a |
| 16 | + */ |
| 17 | + def set(a: A): Mutation[Container] |
| 18 | + |
| 19 | + /** alias to set */ |
| 20 | + def :=(a: A) = set(a) |
| 21 | + |
| 22 | + /** Represent an update operator (like x.y += 1 ) */ |
| 23 | + def modify(f: A => A): Mutation[Container] = c => set(f(get(c)))(c) |
| 24 | + |
| 25 | + /** Composes two lenses, this enables nesting. |
| 26 | + * |
| 27 | + * If our field of type A has a sub-field of type B, then given a lens for it |
| 28 | + * (other: Lens[A, B]) we can create a single lens from Container to B. |
| 29 | + */ |
| 30 | + def compose[B](other: Lens[A, B]): Lens[Container, B] = new Lens[Container, B] { |
| 31 | + def get(c: Container) = other.get(self.get(c)) |
| 32 | + |
| 33 | + def set(b: B) = self.modify(other.set(b)) |
| 34 | + } |
| 35 | + |
| 36 | + /** Given two lenses with the same origin, returns a new lens that can mutate both values |
| 37 | + * represented by both lenses through a tuple. |
| 38 | + */ |
| 39 | + def zip[B](other: Lens[Container, B]): Lens[Container, (A, B)] = new Lens[Container, (A,B)] { |
| 40 | + def get(c: Container): (A,B) = (self.get(c), other.get(c)) |
| 41 | + def set(t: (A, B)): Mutation[Container] = self.set(t._1).andThen(other.set(t._2)) |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +object Lens { |
| 46 | + /* Create a Lens from getter and setter. */ |
| 47 | + def apply[Container, A](getter: Container => A)(setter: (Container, A) => Container): Lens[Container, A] = new Lens[Container, A] { |
| 48 | + def get(c: Container) = getter(c) |
| 49 | + |
| 50 | + def set(a: A): Mutation[Container] = setter(_, a) |
| 51 | + } |
| 52 | + |
| 53 | + /** This is the unit lens, with respect to the compose operation defined above. That is, |
| 54 | + * len.compose(unit) == len == unit.compose(len) |
| 55 | + * |
| 56 | + * More practically, you can view it as a len that mutates the entire object, instead of |
| 57 | + * just a field of it: get() gives the original object, and set() returns the assigned value, |
| 58 | + * no matter what the original value was. |
| 59 | + */ |
| 60 | + def unit[U]: Lens[U, U] = Lens(identity[U])((c, v) => v) |
| 61 | + |
| 62 | + /** Implicit that adds some syntactic sugar if our lens watches a Seq-like collection. */ |
| 63 | + implicit class SeqLikeLens[U, A, Coll[A] <: collection.SeqLike[A, Coll[A]]](val lens: Lens[U, Coll[A]]) extends AnyVal { |
| 64 | + type CBF = collection.generic.CanBuildFrom[Coll[A], A, Coll[A]] |
| 65 | + |
| 66 | + private def field(getter: Coll[A] => A)(setter: (Coll[A], A) => Coll[A]): Lens[U, A] = |
| 67 | + lens.compose[A](Lens[Coll[A], A](getter)(setter)) |
| 68 | + |
| 69 | + def apply(i: Int)(implicit cbf: CBF): Lens[U, A] = field(_.apply(i))((c, v) => c.updated(i, v)) |
| 70 | + |
| 71 | + def head(implicit cbf: CBF): Lens[U, A] = apply(0) |
| 72 | + |
| 73 | + def last(implicit cbf: CBF): Lens[U, A] = field(_.last)((c, v) => c.updated(c.size - 1, v)) |
| 74 | + |
| 75 | + def :+=(item: A)(implicit cbf: CBF) = lens.modify(_ :+ item) |
| 76 | + |
| 77 | + def :++=(item: scala.collection.GenTraversableOnce[A])(implicit cbf: CBF) = lens.modify(_ ++ item) |
| 78 | + |
| 79 | + def foreach(f: Lens[A, A] => Mutation[A])(implicit cbf: CBF): Mutation[U] = |
| 80 | + lens.modify(s => s.map { |
| 81 | + (m: A) => |
| 82 | + val field: Lens[A, A] = Lens.unit[A] |
| 83 | + val p: Mutation[A] = f(field) |
| 84 | + p(m) |
| 85 | + }) |
| 86 | + } |
| 87 | + |
| 88 | + /** Implicit that adds some syntactic sugar if our lens watches a Set-like collection. */ |
| 89 | + implicit class SetLikeLens[U, A, Coll[A] <: collection.SetLike[A, Coll[A]] with Set[A]](val lens: Lens[U, Coll[A]]) extends AnyVal { |
| 90 | + type CBF = collection.generic.CanBuildFrom[Coll[A], A, Coll[A]] |
| 91 | + |
| 92 | + private def field(getter: Coll[A] => A)(setter: (Coll[A], A) => Coll[A]): Lens[U, A] = |
| 93 | + lens.compose[A](Lens[Coll[A], A](getter)(setter)) |
| 94 | + |
| 95 | + def :+=(item: A)(implicit cbf: CBF) = lens.modify(_ + item) |
| 96 | + |
| 97 | + def :++=(item: scala.collection.GenTraversableOnce[A])(implicit cbf: CBF) = lens.modify(_ ++ item) |
| 98 | + |
| 99 | + def foreach(f: Lens[A, A] => Mutation[A])(implicit cbf: CBF): Mutation[U] = |
| 100 | + lens.modify(s => s.map { |
| 101 | + (m: A) => |
| 102 | + val field: Lens[A, A] = Lens.unit[A] |
| 103 | + val p: Mutation[A] = f(field) |
| 104 | + p(m) |
| 105 | + }) |
| 106 | + } |
| 107 | + |
| 108 | + /** Implicit that adds some syntactic sugar if our lens watches an Option[_]. */ |
| 109 | + implicit class OptLens[U, A](val lens: Lens[U, Option[A]]) extends AnyVal { |
| 110 | + def inplaceMap(f: Lens[A, A] => Mutation[A]) = |
| 111 | + lens.modify(opt => opt.map { |
| 112 | + (m: A) => |
| 113 | + val field: Lens[A, A] = Lens.unit[A] |
| 114 | + val p: Mutation[A] = f(field) |
| 115 | + p(m) |
| 116 | + }) |
| 117 | + } |
| 118 | + |
| 119 | + /** Implicit that adds some syntactic sugar if our lens watches a Map[_, _]. */ |
| 120 | + implicit class MapLens[U, A, B](val lens: Lens[U, Map[A, B]]) extends AnyVal { |
| 121 | + def apply(key: A): Lens[U, B] = lens.compose(Lens[Map[A, B], B](_.apply(key))((map, value) => map.updated(key, value))) |
| 122 | + |
| 123 | + def :+=(pair: (A, B)) = lens.modify(_ + pair) |
| 124 | + |
| 125 | + def :++=(item: Iterable[(A, B)]) = lens.modify(_ ++ item) |
| 126 | + |
| 127 | + def foreach(f: Lens[(A, B), (A, B)] => Mutation[(A, B)]): Mutation[U] = |
| 128 | + lens.modify(s => s.map { |
| 129 | + (pair: (A, B)) => |
| 130 | + val field: Lens[(A, B), (A, B)] = Lens.unit[(A, B)] |
| 131 | + val p: Mutation[(A, B)] = f(field) |
| 132 | + p(pair) |
| 133 | + }) |
| 134 | + |
| 135 | + def foreachValue(f: Lens[B, B] => Mutation[B]): Mutation[U] = |
| 136 | + lens.modify(s => s.mapValues { |
| 137 | + (m: B) => |
| 138 | + val field: Lens[B, B] = Lens.unit[B] |
| 139 | + val p: Mutation[B] = f(field) |
| 140 | + p(m) |
| 141 | + }) |
| 142 | + |
| 143 | + def mapValues(f: B => B) = foreachValue(_.modify(f)) |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +/** Represents a lens that has sub-lenses. */ |
| 148 | +class ObjectLens[U, Container](self: Lens[U, Container]) extends Lens[U, Container] { |
| 149 | + /** Creates a sub-lens */ |
| 150 | + def field[A](lens: Lens[Container, A]): Lens[U, A] = self.compose(lens) |
| 151 | + |
| 152 | + /** Creates a sub-lens */ |
| 153 | + def field[A](getter: Container => A)(setter: (Container, A) => Container): Lens[U, A] = |
| 154 | + field(Lens(getter)(setter)) |
| 155 | + |
| 156 | + override def get(u: U) = self.get(u) |
| 157 | + |
| 158 | + override def set(c: Container) = self.set(c) |
| 159 | + |
| 160 | + def update(ms: (Lens[Container, Container] => Mutation[Container])*): Mutation[U] = |
| 161 | + u => set(ms.foldLeft[Container](get(u))((p, m) => m(Lens.unit[Container])(p)))(u) |
| 162 | +} |
| 163 | + |
| 164 | +trait Updatable[A] extends Any { |
| 165 | + self: A => |
| 166 | + def update(ms: (Lens[A, A] => Mutation[A])*): A = ms.foldLeft[A](self)((p, m) => m(Lens.unit[A])(p)) |
| 167 | +} |
0 commit comments