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