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