xref: /aosp_15_r20/frameworks/base/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Maybe.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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 kotlin.coroutines.Continuation
22 import kotlin.coroutines.CoroutineContext
23 import kotlin.coroutines.EmptyCoroutineContext
24 import kotlin.coroutines.RestrictsSuspension
25 import kotlin.coroutines.resume
26 import kotlin.coroutines.startCoroutine
27 import kotlin.coroutines.suspendCoroutine
28 
29 /** Represents a value that may or may not be present. */
30 sealed class Maybe<out A>
31 
32 /** A [Maybe] value that is present. */
33 data class Just<out A> internal constructor(val value: A) : Maybe<A>()
34 
35 /** A [Maybe] value that is not present. */
36 data object None : Maybe<Nothing>()
37 
38 /** Utilities to query [Maybe] instances from within a [maybe] block. */
39 @RestrictsSuspension
40 object MaybeScope {
41     suspend operator fun <A> Maybe<A>.not(): A = suspendCoroutine { k ->
42         if (this is Just) k.resume(value)
43     }
44 
45     suspend inline fun guard(crossinline block: () -> Boolean): Unit = suspendCoroutine { k ->
46         if (block()) k.resume(Unit)
47     }
48 }
49 
50 /**
51  * Returns a [Maybe] value produced by evaluating [block].
52  *
53  * [block] can use its [MaybeScope] receiver to query other [Maybe] values, automatically cancelling
54  * execution of [block] and producing [None] when attempting to query a [Maybe] that is not present.
55  *
56  * This can be used instead of Kotlin's built-in nullability (`?.` and `?:`) operators when dealing
57  * with complex combinations of nullables:
58  * ``` kotlin
59  * val aMaybe: Maybe<Any> = ...
60  * val bMaybe: Maybe<Any> = ...
61  * val result: String = maybe {
62  *   val a = !aMaybe
63  *   val b = !bMaybe
64  *   "Got: $a and $b"
65  * }
66  * ```
67  */
maybenull68 fun <A> maybe(block: suspend MaybeScope.() -> A): Maybe<A> {
69     var maybeResult: Maybe<A> = None
70     val k =
71         object : Continuation<A> {
72             override val context: CoroutineContext = EmptyCoroutineContext
73 
74             override fun resumeWith(result: Result<A>) {
75                 maybeResult = result.getOrNull()?.let { just(it) } ?: None
76             }
77         }
78     block.startCoroutine(MaybeScope, k)
79     return maybeResult
80 }
81 
82 /** Returns a [Just] containing this value, or [None] if `null`. */
toMaybenull83 inline fun <A> (A?).toMaybe(): Maybe<A> = maybe(this)
84 
85 /** Returns a [Just] containing a non-null [value], or [None] if `null`. */
86 inline fun <A> maybe(value: A?): Maybe<A> = value?.let(::just) ?: None
87 
88 /** Returns a [Just] containing [value]. */
89 fun <A> just(value: A): Maybe<A> = Just(value)
90 
91 /** A [Maybe] that is not present. */
92 val none: Maybe<Nothing> = None
93 
94 /** A [Maybe] that is not present. */
95 inline fun <A> none(): Maybe<A> = None
96 
97 /** Returns the value present in this [Maybe], or `null` if not present. */
98 inline fun <A> Maybe<A>.orNull(): A? = orElse(null)
99 
100 /**
101  * Returns a [Maybe] holding the result of applying [transform] to the value in the original
102  * [Maybe].
103  */
104 inline fun <A, B> Maybe<A>.map(transform: (A) -> B): Maybe<B> =
105     when (this) {
106         is Just -> just(transform(value))
107         is None -> None
108     }
109 
110 /** Returns the result of applying [transform] to the value in the original [Maybe]. */
flatMapnull111 inline fun <A, B> Maybe<A>.flatMap(transform: (A) -> Maybe<B>): Maybe<B> =
112     when (this) {
113         is Just -> transform(value)
114         is None -> None
115     }
116 
117 /** Returns the value present in this [Maybe], or the result of [defaultValue] if not present. */
orElseGetnull118 inline fun <A> Maybe<A>.orElseGet(defaultValue: () -> A): A =
119     when (this) {
120         is Just -> value
121         is None -> defaultValue()
122     }
123 
124 /**
125  * Returns the value present in this [Maybe], or invokes [error] with the message returned from
126  * [getMessage].
127  */
<lambda>null128 inline fun <A> Maybe<A>.orError(getMessage: () -> Any): A = orElseGet { error(getMessage()) }
129 
130 /** Returns the value present in this [Maybe], or [defaultValue] if not present. */
orElsenull131 inline fun <A> Maybe<A>.orElse(defaultValue: A): A =
132     when (this) {
133         is Just -> value
134         is None -> defaultValue
135     }
136 
137 /**
138  * Returns a [Maybe] that contains the present in the original [Maybe], only if it satisfies
139  * [predicate].
140  */
filternull141 inline fun <A> Maybe<A>.filter(predicate: (A) -> Boolean): Maybe<A> =
142     when (this) {
143         is Just -> if (predicate(value)) this else None
144         else -> this
145     }
146 
147 /** Returns a [List] containing all values that are present in this [Iterable]. */
filterJustnull148 fun <A> Iterable<Maybe<A>>.filterJust(): List<A> = asSequence().filterJust().toList()
149 
150 /** Returns a [List] containing all values that are present in this [Sequence]. */
151 fun <A> Sequence<Maybe<A>>.filterJust(): Sequence<A> = filterIsInstance<Just<A>>().map { it.value }
152 
153 // Align
154 
155 /**
156  * Returns a [Maybe] containing the result of applying the values present in the original [Maybe]
157  * and other, applied to [transform] as a [These].
158  */
alignWithnull159 inline fun <A, B, C> Maybe<A>.alignWith(other: Maybe<B>, transform: (These<A, B>) -> C): Maybe<C> =
160     when (this) {
161         is Just -> {
162             val a = value
163             when (other) {
164                 is Just -> {
165                     val b = other.value
166                     just(transform(These.both(a, b)))
167                 }
168                 None -> just(transform(These.thiz(a)))
169             }
170         }
171         None ->
172             when (other) {
173                 is Just -> {
174                     val b = other.value
175                     just(transform(These.that(b)))
176                 }
177                 None -> none
178             }
179     }
180 
181 // Alt
182 
183 /** Returns a [Maybe] containing the value present in the original [Maybe], or [other]. */
<lambda>null184 infix fun <A> Maybe<A>.orElseMaybe(other: Maybe<A>): Maybe<A> = orElseGetMaybe { other }
185 
186 /**
187  * Returns a [Maybe] containing the value present in the original [Maybe], or the result of [other].
188  */
orElseGetMaybenull189 inline fun <A> Maybe<A>.orElseGetMaybe(other: () -> Maybe<A>): Maybe<A> =
190     when (this) {
191         is Just -> this
192         else -> other()
193     }
194 
195 // Apply
196 
197 /**
198  * Returns a [Maybe] containing the value present in [argMaybe] applied to the function present in
199  * the original [Maybe].
200  */
applynull201 fun <A, B> Maybe<(A) -> B>.apply(argMaybe: Maybe<A>): Maybe<B> = flatMap { f ->
202     argMaybe.map { a -> f(a) }
203 }
204 
205 /**
206  * Returns a [Maybe] containing the result of applying [transform] to the values present in the
207  * original [Maybe] and [other].
208  */
anull209 inline fun <A, B, C> Maybe<A>.zipWith(other: Maybe<B>, transform: (A, B) -> C) = flatMap { a ->
210     other.map { b -> transform(a, b) }
211 }
212 
213 // Bind
214 
215 /**
216  * Returns a [Maybe] containing the value present in the [Maybe] present in the original [Maybe].
217  */
flattennull218 fun <A> Maybe<Maybe<A>>.flatten(): Maybe<A> = flatMap { it }
219 
220 // Semigroup
221 
222 /**
223  * Returns a [Maybe] containing the result of applying the values present in the original [Maybe]
224  * and other, applied to [transform].
225  */
mergeWithnull226 fun <A> Maybe<A>.mergeWith(other: Maybe<A>, transform: (A, A) -> A): Maybe<A> =
227     alignWith(other) { it.merge(transform) }
228 
229 /**
230  * Returns a list containing only the present results of applying [transform] to each element in the
231  * original iterable.
232  */
mapMaybenull233 fun <A, B> Iterable<A>.mapMaybe(transform: (A) -> Maybe<B>): List<B> =
234     asSequence().mapMaybe(transform).toList()
235 
236 /**
237  * Returns a sequence containing only the present results of applying [transform] to each element in
238  * the original sequence.
239  */
240 fun <A, B> Sequence<A>.mapMaybe(transform: (A) -> Maybe<B>): Sequence<B> =
241     map(transform).filterIsInstance<Just<B>>().map { it.value }
242 
243 /**
244  * Returns a map with values of only the present results of applying [transform] to each entry in
245  * the original map.
246  */
mapMaybeValuesnull247 inline fun <K, A, B> Map<K, A>.mapMaybeValues(
248     crossinline p: (Map.Entry<K, A>) -> Maybe<B>
249 ): Map<K, B> = asSequence().mapMaybe { entry -> p(entry).map { entry.key to it } }.toMap()
250 
251 /** Returns a map with all non-present values filtered out. */
filterJustValuesnull252 fun <K, A> Map<K, Maybe<A>>.filterJustValues(): Map<K, A> =
253     asSequence().mapMaybe { (key, mValue) -> mValue.map { key to it } }.toMap()
254 
255 /**
256  * Returns a pair of [Maybes][Maybe] that contain the [Pair.first] and [Pair.second] values present
257  * in the original [Maybe].
258  */
splitPairnull259 fun <A, B> Maybe<Pair<A, B>>.splitPair(): Pair<Maybe<A>, Maybe<B>> =
260     map { it.first } to map { it.second }
261 
262 /** Returns the value associated with [key] in this map as a [Maybe]. */
getMaybenull263 fun <K, V> Map<K, V>.getMaybe(key: K): Maybe<V> {
264     val value = get(key)
265     if (value == null && !containsKey(key)) {
266         return none
267     } else {
268         @Suppress("UNCHECKED_CAST")
269         return just(value as V)
270     }
271 }
272