1 /*
<lambda>null2 * Copyright (C) 2024 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 @file:Suppress("NOTHING_TO_INLINE", "SuspendCoroutine")
18
19 package com.android.systemui.kairos.util
20
21 import com.android.systemui.kairos.util.Maybe.Absent
22 import com.android.systemui.kairos.util.Maybe.Present
23 import kotlin.coroutines.Continuation
24 import kotlin.coroutines.CoroutineContext
25 import kotlin.coroutines.EmptyCoroutineContext
26 import kotlin.coroutines.RestrictsSuspension
27 import kotlin.coroutines.resume
28 import kotlin.coroutines.startCoroutine
29 import kotlin.coroutines.suspendCoroutine
30
31 /** Represents a value that may or may not be present. */
32 sealed interface Maybe<out A> {
33 /** A [Maybe] value that is present. */
34 @JvmInline value class Present<out A> internal constructor(val value: A) : Maybe<A>
35
36 /** A [Maybe] value that is not present. */
37 data object Absent : Maybe<Nothing>
38
39 companion object {
40 /** Returns a [Maybe] containing [value]. */
41 fun <A> present(value: A): Maybe<A> = Present(value)
42
43 /** A [Maybe] that is not present. */
44 val absent: Maybe<Nothing> = Absent
45
46 /** A [Maybe] that is not present. */
47 inline fun <A> absent(): Maybe<A> = Absent
48 }
49 }
50
51 /** Utilities to query [Maybe] instances from within a [maybe] block. */
52 @RestrictsSuspension
53 object MaybeScope {
notnull54 suspend operator fun <A> Maybe<A>.not(): A = suspendCoroutine { k ->
55 if (this is Present) k.resume(value)
56 }
57
guardnull58 suspend inline fun guard(crossinline block: () -> Boolean): Unit = suspendCoroutine { k ->
59 if (block()) k.resume(Unit)
60 }
61 }
62
63 /**
64 * Returns a [Maybe] value produced by evaluating [block].
65 *
66 * [block] can use its [MaybeScope] receiver to query other [Maybe] values, automatically cancelling
67 * execution of [block] and producing [Absent] when attempting to query a [Maybe] that is not
68 * present.
69 *
70 * This can be used instead of Kotlin's built-in nullability (`?.` and `?:`) operators when dealing
71 * with complex combinations of nullables:
72 * ```
73 * val aMaybe: Maybe<Any> = ...
74 * val bMaybe: Maybe<Any> = ...
75 * val result: String = maybe {
76 * val a = !aMaybe
77 * val b = !bMaybe
78 * "Got: $a and $b"
79 * }
80 * ```
81 */
maybenull82 fun <A> maybe(block: suspend MaybeScope.() -> A): Maybe<A> {
83 var maybeResult: Maybe<A> = Absent
84 val k =
85 object : Continuation<A> {
86 override val context: CoroutineContext = EmptyCoroutineContext
87
88 override fun resumeWith(result: Result<A>) {
89 maybeResult = result.getOrNull()?.let { Maybe.present(it) } ?: Absent
90 }
91 }
92 block.startCoroutine(MaybeScope, k)
93 return maybeResult
94 }
95
96 /** Returns a [Maybe] containing this value if it is not `null`. */
toMaybenull97 inline fun <A> (A?).toMaybe(): Maybe<A> = maybe(this)
98
99 /** Returns a [Maybe] containing [value] if it is not `null`. */
100 inline fun <A> maybe(value: A?): Maybe<A> = value?.let { Maybe.present(it) } ?: Absent
101
102 /** Returns a [Maybe] that is absent. */
maybeOfnull103 fun <A> maybeOf(): Maybe<A> = Absent
104
105 /** Returns a [Maybe] containing [value]. */
106 fun <A> maybeOf(value: A): Maybe<A> = Present(value)
107
108 /** Returns the value present in this [Maybe], or `null` if not present. */
109 inline fun <A> Maybe<A>.orNull(): A? = orElse(null)
110
111 /**
112 * Returns a [Maybe] holding the result of applying [transform] to the value in the original
113 * [Maybe].
114 */
115 inline fun <A, B> Maybe<A>.map(transform: (A) -> B): Maybe<B> =
116 when (this) {
117 is Present -> Maybe.present(transform(value))
118 is Absent -> Absent
119 }
120
121 /** Returns the result of applying [transform] to the value in the original [Maybe]. */
flatMapnull122 inline fun <A, B> Maybe<A>.flatMap(transform: (A) -> Maybe<B>): Maybe<B> =
123 when (this) {
124 is Present -> transform(value)
125 is Absent -> Absent
126 }
127
128 /** Returns the value present in this [Maybe], or the result of [defaultValue] if not present. */
orElseGetnull129 inline fun <A> Maybe<A>.orElseGet(defaultValue: () -> A): A =
130 when (this) {
131 is Present -> value
132 is Absent -> defaultValue()
133 }
134
135 /**
136 * Returns the value present in this [Maybe], or invokes [error] with the message returned from
137 * [getMessage].
138 */
<lambda>null139 inline fun <A> Maybe<A>.orError(getMessage: () -> Any): A = orElseGet { error(getMessage()) }
140
141 /** Returns the value present in this [Maybe], or [defaultValue] if not present. */
orElsenull142 inline fun <A> Maybe<A>.orElse(defaultValue: A): A =
143 when (this) {
144 is Present -> value
145 is Absent -> defaultValue
146 }
147
148 /**
149 * Returns a [Maybe] that contains the present in the original [Maybe], only if it satisfies
150 * [predicate].
151 */
filternull152 inline fun <A> Maybe<A>.filter(predicate: (A) -> Boolean): Maybe<A> =
153 when (this) {
154 is Present -> if (predicate(value)) this else Absent
155 else -> this
156 }
157
158 /** Returns a [List] containing all values that are present in this [Iterable]. */
filterPresentnull159 fun <A> Iterable<Maybe<A>>.filterPresent(): List<A> = asSequence().filterPresent().toList()
160
161 /** Returns a [List] containing all values that are present in this [Sequence]. */
162 fun <A> Sequence<Maybe<A>>.filterPresent(): Sequence<A> =
163 filterIsInstance<Present<A>>().map { it.value }
164
165 // Align
166
167 /**
168 * Returns a [Maybe] containing the result of applying the values present in the original [Maybe]
169 * and other, applied to [transform] as a [These].
170 */
alignWithnull171 inline fun <A, B, C> Maybe<A>.alignWith(other: Maybe<B>, transform: (These<A, B>) -> C): Maybe<C> =
172 when (this) {
173 is Present -> {
174 val a = value
175 when (other) {
176 is Present -> {
177 val b = other.value
178 Maybe.present(transform(These.both(a, b)))
179 }
180
181 Absent -> Maybe.present(transform(These.first(a)))
182 }
183 }
184 Absent ->
185 when (other) {
186 is Present -> {
187 val b = other.value
188 Maybe.present(transform(These.second(b)))
189 }
190
191 Absent -> Maybe.absent
192 }
193 }
194
195 // Alt
196
197 /** Returns a [Maybe] containing the value present in the original [Maybe], or [other]. */
<lambda>null198 infix fun <A> Maybe<A>.orElseMaybe(other: Maybe<A>): Maybe<A> = orElseGetMaybe { other }
199
200 /**
201 * Returns a [Maybe] containing the value present in the original [Maybe], or the result of [other].
202 */
orElseGetMaybenull203 inline fun <A> Maybe<A>.orElseGetMaybe(other: () -> Maybe<A>): Maybe<A> =
204 when (this) {
205 is Present -> this
206 else -> other()
207 }
208
209 // Apply
210
211 /**
212 * Returns a [Maybe] containing the value present in [argMaybe] applied to the function present in
213 * the original [Maybe].
214 */
applynull215 fun <A, B> Maybe<(A) -> B>.apply(argMaybe: Maybe<A>): Maybe<B> = flatMap { f ->
216 argMaybe.map { a -> f(a) }
217 }
218
219 /**
220 * Returns a [Maybe] containing the result of applying [transform] to the values present in the
221 * original [Maybe] and [other].
222 */
anull223 inline fun <A, B, C> Maybe<A>.zipWith(other: Maybe<B>, transform: (A, B) -> C) = flatMap { a ->
224 other.map { b -> transform(a, b) }
225 }
226
227 // Bind
228
229 /**
230 * Returns a [Maybe] containing the value present in the [Maybe] present in the original [Maybe].
231 */
flattennull232 fun <A> Maybe<Maybe<A>>.flatten(): Maybe<A> = flatMap { it }
233
234 // Semigroup
235
236 /**
237 * Returns a [Maybe] containing the result of applying the values present in the original [Maybe]
238 * and other, applied to [transform].
239 */
mergeWithnull240 fun <A> Maybe<A>.mergeWith(other: Maybe<A>, transform: (A, A) -> A): Maybe<A> =
241 alignWith(other) { it.merge(transform) }
242
243 /**
244 * Returns a list containing only the present results of applying [transform] to each element in the
245 * original iterable.
246 */
<lambda>null247 inline fun <A, B> Iterable<A>.mapMaybe(transform: (A) -> Maybe<B>): List<B> = buildList {
248 for (a in this@mapMaybe) {
249 val result = transform(a)
250 if (result is Present) {
251 add(result.value)
252 }
253 }
254 }
255
256 /**
257 * Returns a sequence containing only the present results of applying [transform] to each element in
258 * the original sequence.
259 */
mapMaybenull260 fun <A, B> Sequence<A>.mapMaybe(transform: (A) -> Maybe<B>): Sequence<B> =
261 map(transform).filterIsInstance<Present<B>>().map { it.value }
262
263 /**
264 * Returns a map with values of only the present results of applying [transform] to each entry in
265 * the original map.
266 */
mapMaybeValuesnull267 inline fun <K, A, B> Map<K, A>.mapMaybeValues(transform: (Map.Entry<K, A>) -> Maybe<B>): Map<K, B> =
268 buildMap {
269 for (entry in this@mapMaybeValues) {
270 val result = transform(entry)
271 if (result is Present) {
272 put(entry.key, result.value)
273 }
274 }
275 }
276
277 /** Returns a map with all non-present values filtered out. */
filterPresentValuesnull278 fun <K, A> Map<K, Maybe<A>>.filterPresentValues(): Map<K, A> =
279 asSequence().mapMaybe { (key, mValue) -> mValue.map { key to it } }.toMap()
280
281 /**
282 * Returns a pair of [Maybes][Maybe] that contain the [Pair.first] and [Pair.second] values present
283 * in the original [Maybe].
284 */
splitPairnull285 fun <A, B> Maybe<Pair<A, B>>.splitPair(): Pair<Maybe<A>, Maybe<B>> =
286 map { it.first } to map { it.second }
287
288 /** Returns the value associated with [key] in this map as a [Maybe]. */
getMaybenull289 fun <K, V> Map<K, V>.getMaybe(key: K): Maybe<V> {
290 val value = get(key)
291 if (value == null && !containsKey(key)) {
292 return Maybe.absent
293 } else {
294 @Suppress("UNCHECKED_CAST")
295 return Maybe.present(value as V)
296 }
297 }
298