1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.libraries.pcc.chronicle.api.optics
18 
19 import com.android.libraries.pcc.chronicle.api.operation.Action
20 
21 /**
22  * A functional optic which provides "focus" on a part of an entity (the "original field" of the
23  * "original entity") and allows for accessing its value, as well as creating functions to turn
24  * source entities into the instances of the [targetEntityType] by operating on the "source field"
25  * to return values for the "target field".
26  *
27  * ### Explanation of the type parameters:
28  *
29  * The names of these type parameters follow from conventions set-out in other functional
30  * programming libraries and literature.
31  *
32  * * [S]
33  * - the source type, or: what is being examined.
34  * * [T]
35  * - the target type, or: what is created and returned by [lift] or [lift]'s functions.
36  * * [A]
37  * - the type of the field focused-upon within the source type
38  * * [B]
39  * - the type of the field whose value is derived-from the field of type [A] in [lift].
40  *
41  * ### What is it used for?
42  *
43  * See [the cantrips design doc](go/chronicle-cantrips).
44  *
45  * ### Examples
46  *
47  * For concrete examples of lenses being used, please see the tests in `LensTest`.
48  */
49 abstract class Lens<S, T, A, B>(
50   val sourceAccessPath: OpticalAccessPath,
51   val targetAccessPath: OpticalAccessPath,
52   val sourceEntityType: Class<out S>,
53   val targetEntityType: Class<out T>,
54   val sourceFieldType: Class<out A>,
55   val targetFieldType: Class<out B>
56 ) {
57   /** Whether or not the [Lens] is actually monomorphic. */
58   val isMonomorphic: Boolean =
59     sourceEntityType == targetEntityType && sourceFieldType == targetFieldType
60 
61   /** Representation of this [Lens] as a [Traversal]. */
<lambda>null62   private val traversalRepresentation: Traversal<S, T, A, B> by lazy {
63     object :
64       Traversal<S, T, A, B>(
65         sourceAccessPath = sourceAccessPath,
66         targetAccessPath = targetAccessPath,
67         sourceEntityType = sourceEntityType,
68         targetEntityType = targetEntityType,
69         sourceFieldType = sourceFieldType,
70         targetFieldType = targetFieldType
71       ) {
72       override fun every(entity: S): Sequence<A> =
73         if (entity != null) sequenceOf(get(entity)) else emptySequence()
74 
75       override fun modify(entity: S, modifier: (A) -> B): T = this@Lens.modify(entity, modifier)
76 
77       override fun modifyWithAction(
78         entity: S,
79         modifier: (value: A) -> Action<out B>
80       ): Action<out T> = this@Lens.modifyWithAction(entity, modifier)
81 
82       override fun toString(): String = this@Lens.toString()
83     }
84   }
85 
86   /** Returns the value of the field accessed by the [sourceAccessPath] in the provided [entity]. */
getnull87   abstract fun get(entity: S): A
88 
89   /** Returns a new [T] based on [entity] with the target field set to [newValue] */
90   abstract fun set(entity: S, newValue: B): T
91 
92   /**
93    * Returns a function capable of turning an [S] into a [T] where the [mapping] is used to
94    * determine how to interpret an [A] as a [B].
95    *
96    * In functional parliance: "lift a function from [A] to [B] to the context of [S] and [T]"
97    *
98    * For example:
99    *
100    * ```
101    * data class Foo(val x: Int)
102    * data class Bar(val y: Boolean)
103    *
104    * val FooToBar_By_XAndY = Lens.create<Foo, Bar, Int, Boolean>(..) {..}
105    *
106    * val fooToBarWithEvenOdd = FooToBarByXAndY.lift { x -> x % 2 == 0 }
107    *
108    * fooToBarWithEvenOdd(Foo(7))  // returns: Bar(false)
109    * fooToBarWithEvenOdd(Foo(10)) // returns: Bar(true)
110    * ```
111    */
112   inline fun lift(crossinline mapping: (A) -> B): (S) -> T = { set(it, mapping(get(it))) }
113 
114   /**
115    * Applies the modifier of the focused field in the [entity] to build and return a new target
116    * entity [T].
117    *
118    * For example:
119    *
120    * ```
121    * data class Foo(val x: Int)
122    * data class Bar(val y: Boolean)
123    *
124    * val FooToBar_By_XAndY = PolymporphicLens.create<Foo, Bar, Int, Boolean>(..)
125    *
126    * val bar1 = modify(Foo(7)) { it % 2 == 0 } // Bar(false)
127    * val bar2 = modify(Foo(7)) { it % 2 != 0 } // Bar(true)
128    * ```
129    */
modifynull130   inline fun modify(entity: S, crossinline modifier: (A) -> B): T = lift(modifier)(entity)
131 
132   /** Similar to [modify], except the value can take the form of an [Action]. */
133   fun modifyWithAction(entity: S, modifier: (value: A) -> Action<out B>): Action<out T> {
134     val original = get(entity)
135     return when (val res = modifier(original)) {
136       is Action.OmitFromParent -> res
137       is Action.OmitFromRoot -> res
138       is Action.Throw -> res
139       is Action.Update -> Action.Update(set(entity, res.newValue))
140     }
141   }
142 
143   /**
144    * Composes the receiver/LHS with the [other] [Lens], providing the ability to focus deeper into
145    * the [sourceEntityType] of the receiver when fetching values with [get] or converting to the
146    * [targetEntityType] with [lift] or [lift].
147    *
148    * For example:
149    *
150    * ```kotlin
151    * // A lens which lets us get or set a person's pet.
152    * val personPetLens: Lens<Person, Person, Pet, Pet>
153    * // A lens which lets us get or set a pet's name.
154    * val petNameLens: Lens<Pet, Pet, String, String>
155    *
156    * // Composing the two lenses above gives us one capable of finding/changing the name of a
157    * // person's pet.
158    * val personPetNameLens: Lens<Person, Person, String, String> =
159    *   personPetLens compose petNameLens
160    * ```
161    */
162   @Suppress("UNCHECKED_CAST") // This composition does actually do checking, using `canCompose`.
composenull163   infix fun <AIn : A, BIn : B, NewA, NewB> compose(
164     other: Lens<AIn, BIn, NewA, NewB>
165   ): Lens<S, T, NewA, NewB> {
166     require(this canCompose other) { "$this cannot compose with $other" }
167 
168     return object :
169       Lens<S, T, NewA, NewB>(
170         sourceAccessPath = this@Lens.sourceAccessPath compose other.sourceAccessPath,
171         targetAccessPath = this@Lens.targetAccessPath compose other.targetAccessPath,
172         sourceEntityType = this@Lens.sourceEntityType,
173         targetEntityType = this@Lens.targetEntityType,
174         sourceFieldType = other.sourceFieldType,
175         targetFieldType = other.targetFieldType
176       ) {
177       override fun get(entity: S): NewA = other.get(this@Lens.get(entity) as AIn)
178       override fun set(entity: S, newValue: NewB): T =
179         this@Lens.set(
180           entity = entity,
181           newValue = other.set(entity = this@Lens.get(entity) as AIn, newValue = newValue)
182         )
183     }
184   }
185 
186   /** Returns a [Traversal] representation of this [Lens]. */
asTraversalnull187   fun asTraversal(): Traversal<S, T, A, B> = traversalRepresentation
188 
189   /**
190    * Returns whether or not the receiving [Lens] can compose with the [other] [Lens].
191    *
192    * A [Lens] can compose with another if and only if the original entity type of the [other] can be
193    * assigned to the original field of the entity targeted by the LHS ***and*** if the modified
194    * entity type of the [other] can be assigned to the modified field of the entity targeted by the
195    * LHS.
196    */
197   infix fun canCompose(other: Lens<*, *, *, *>): Boolean =
198     sourceFieldType.isAssignableFrom(other.sourceEntityType) &&
199       targetFieldType.isAssignableFrom(other.targetEntityType)
200 
201   override fun toString(): String {
202     if (isMonomorphic) return "Lens($sourceAccessPath)"
203     return "Lens($sourceAccessPath -> $targetAccessPath)"
204   }
205 
206   companion object {
207     /** Helper factory function to create a monomorphic instance of a [Lens]. */
createnull208     inline fun <reified Entity, reified Focus> create(
209       focusAccessPath: OpticalAccessPath,
210       crossinline getter: (Entity) -> Focus,
211       crossinline setter: (Entity, Focus) -> Entity
212     ): Lens<Entity, Entity, Focus, Focus> {
213       return object :
214         Lens<Entity, Entity, Focus, Focus>(
215           sourceAccessPath = focusAccessPath,
216           targetAccessPath = focusAccessPath,
217           sourceEntityType = Entity::class.java,
218           targetEntityType = Entity::class.java,
219           sourceFieldType = Focus::class.java,
220           targetFieldType = Focus::class.java
221         ) {
222         override fun get(entity: Entity): Focus = getter(entity)
223         override fun set(entity: Entity, newValue: Focus): Entity = setter(entity, newValue)
224       }
225     }
226 
227     /** Helper factory function to create a polymorphic instance of a [Lens]. */
createnull228     inline fun <reified S, reified T, reified A, reified B> create(
229       sourceAccessPath: OpticalAccessPath,
230       targetAccessPath: OpticalAccessPath,
231       crossinline getter: (S) -> A,
232       crossinline setter: (S, B) -> T
233     ): Lens<S, T, A, B> {
234       return object :
235         Lens<S, T, A, B>(
236           sourceAccessPath = sourceAccessPath,
237           targetAccessPath = targetAccessPath,
238           sourceEntityType = S::class.java,
239           targetEntityType = T::class.java,
240           sourceFieldType = A::class.java,
241           targetFieldType = B::class.java
242         ) {
243         override fun get(entity: S): A = getter(entity)
244         override fun set(entity: S, newValue: B): T = setter(entity, newValue)
245       }
246     }
247   }
248 }
249