1# FRP Semantics 2 3`Kairos`'s pure API is based off of the following denotational semantics 4([wikipedia](https://en.wikipedia.org/wiki/Denotational_semantics)). 5 6The semantics model `Kairos` types as time-varying values; by making `Time` a 7first-class value, we can define a referentially-transparent API that allows us 8to reason about the behavior of the pure `Kairos` combinators. This is 9implementation-agnostic; we can compare the behavior of any implementation with 10expected behavior denoted by these semantics to identify bugs. 11 12The semantics are written in pseudo-Kotlin; places where we are deviating from 13real Kotlin are noted with comments. 14 15``` kotlin 16 17sealed class Time : Comparable<Time> { 18 object BigBang : Time() 19 data class At(time: BigDecimal) : Time() 20 object Infinity : Time() 21 22 override final fun compareTo(other: Time): Int = 23 when (this) { 24 BigBang -> if (other === BigBang) 0 else -1 25 is At -> when (other) { 26 BigBang -> 1 27 is At -> time.compareTo(other.time) 28 Infinity -> -1 29 } 30 Infinity -> if (other === Infinity) 0 else 1 31 } 32} 33 34typealias Transactional<T> = (Time) -> T 35 36typealias TFlow<T> = SortedMap<Time, T> 37 38private fun <T> SortedMap<Time, T>.pairwise(): List<Pair<Pair<Time, T>, Pair<Time<T>>>> = 39 // NOTE: pretend evaluation is lazy, so that error() doesn't immediately throw 40 (toList() + Pair(Time.Infinity, error("no value"))).zipWithNext() 41 42class TState<T> internal constructor( 43 internal val current: Transactional<T>, 44 val stateChanges: TFlow<T>, 45) 46 47val emptyTFlow: TFlow<Nothing> = emptyMap() 48 49fun <A, B> TFlow<A>.map(f: FrpTransactionScope.(A) -> B): TFlow<B> = 50 mapValues { (t, a) -> FrpTransactionScope(t).f(a) } 51 52fun <A> TFlow<A>.filter(f: FrpTransactionScope.(A) -> Boolean): TFlow<A> = 53 filter { (t, a) -> FrpTransactionScope(t).f(a) } 54 55fun <A> merge( 56 first: TFlow<A>, 57 second: TFlow<A>, 58 onCoincidence: Time.(A, A) -> A, 59): TFlow<A> = 60 first.toMutableMap().also { result -> 61 second.forEach { (t, a) -> 62 result.merge(t, a) { f, s -> 63 FrpTranscationScope(t).onCoincidence(f, a) 64 } 65 } 66 }.toSortedMap() 67 68fun <A> TState<TFlow<A>>.switch(): TFlow<A> { 69 val truncated = listOf(Pair(Time.BigBang, current.invoke(Time.BigBang))) + 70 stateChanges.dropWhile { (time, _) -> time < time0 } 71 val events = 72 truncated 73 .pairwise() 74 .flatMap { ((t0, sa), (t2, _)) -> 75 sa.filter { (t1, a) -> t0 < t1 && t1 <= t2 } 76 } 77 return events.toSortedMap() 78} 79 80fun <A> TState<TFlow<A>>.switchPromptly(): TFlow<A> { 81 val truncated = listOf(Pair(Time.BigBang, current.invoke(Time.BigBang))) + 82 stateChanges.dropWhile { (time, _) -> time < time0 } 83 val events = 84 truncated 85 .pairwise() 86 .flatMap { ((t0, sa), (t2, _)) -> 87 sa.filter { (t1, a) -> t0 <= t1 && t1 <= t2 } 88 } 89 return events.toSortedMap() 90} 91 92typealias GroupedTFlow<K, V> = TFlow<Map<K, V>> 93 94fun <K, V> TFlow<Map<K, V>>.groupByKey(): GroupedTFlow<K, V> = this 95 96fun <K, V> GroupedTFlow<K, V>.eventsForKey(key: K): TFlow<V> = 97 map { m -> m[k] }.filter { it != null }.map { it!! } 98 99fun <A, B> TState<A>.map(f: (A) -> B): TState<B> = 100 TState( 101 current = { t -> f(current.invoke(t)) }, 102 stateChanges = stateChanges.map { f(it) }, 103 ) 104 105fun <A, B, C> TState<A>.combineWith( 106 other: TState<B>, 107 f: (A, B) -> C, 108): TState<C> = 109 TState( 110 current = { t -> f(current.invoke(t), other.current.invoke(t)) }, 111 stateChanges = run { 112 val aChanges = 113 stateChanges 114 .map { a -> 115 val b = other.current.sample() 116 Triple(a, b, f(a, b)) 117 } 118 val bChanges = 119 other 120 .stateChanges 121 .map { b -> 122 val a = current.sample() 123 Triple(a, b, f(a, b)) 124 } 125 merge(aChanges, bChanges) { (a, _, _), (_, b, _) -> 126 Triple(a, b, f(a, b)) 127 } 128 .map { (_, _, zipped) -> zipped } 129 }, 130 ) 131 132fun <A> TState<TState<A>>.flatten(): TState<A> { 133 val changes = 134 stateChanges 135 .pairwise() 136 .flatMap { ((t0, oldInner), (t2, _)) -> 137 val inWindow = 138 oldInner 139 .stateChanges 140 .filter { (t1, b) -> t0 <= t1 && t1 < t2 } 141 if (inWindow.firstOrNull()?.time != t0) { 142 listOf(Pair(t0, oldInner.current.invoke(t0))) + inWindow 143 } else { 144 inWindow 145 } 146 } 147 return TState( 148 current = { t -> current.invoke(t).current.invoke(t) }, 149 stateChanges = changes.toSortedMap(), 150 ) 151} 152 153open class FrpTranscationScope internal constructor( 154 internal val currentTime: Time, 155) { 156 val now: TFlow<Unit> = 157 sortedMapOf(currentTime to Unit) 158 159 fun <A> Transactional<A>.sample(): A = 160 invoke(currentTime) 161 162 fun <A> TState<A>.sample(): A = 163 current.sample() 164} 165 166class FrpStateScope internal constructor( 167 time: Time, 168 internal val stopTime: Time, 169): FrpTransactionScope(time) { 170 171 fun <A, B> TFlow<A>.fold( 172 initialValue: B, 173 f: FrpTransactionScope.(B, A) -> B, 174 ): TState<B> { 175 val truncated = 176 dropWhile { (t, _) -> t < currentTime } 177 .takeWhile { (t, _) -> t <= stopTime } 178 val folded = 179 truncated 180 .scan(Pair(currentTime, initialValue)) { (_, b) (t, a) -> 181 Pair(t, FrpTransactionScope(t).f(a, b)) 182 } 183 val lookup = { t1 -> 184 folded.lastOrNull { (t0, _) -> t0 < t1 }?.value ?: initialValue 185 } 186 return TState(lookup, folded.toSortedMap()) 187 } 188 189 fun <A> TFlow<A>.hold(initialValue: A): TState<A> = 190 fold(initialValue) { _, a -> a } 191 192 fun <K, V> TFlow<Map<K, Maybe<V>>>.foldMapIncrementally( 193 initialValues: Map<K, V> 194 ): TState<Map<K, V>> = 195 fold(initialValues) { patch, map -> 196 val eithers = patch.map { (k, v) -> 197 if (v is Just) Left(k to v.value) else Right(k) 198 } 199 val adds = eithers.filterIsInstance<Left>().map { it.left } 200 val removes = eithers.filterIsInstance<Right>().map { it.right } 201 val removed: Map<K, V> = map - removes.toSet() 202 val updated: Map<K, V> = removed + adds 203 updated 204 } 205 206 fun <K : Any, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementally( 207 initialTFlows: Map<K, TFlow<V>>, 208 ): TFlow<Map<K, V>> = 209 foldMapIncrementally(initialTFlows).map { it.merge() }.switch() 210 211 fun <K, A, B> TFlow<Map<K, Maybe<A>>.mapLatestStatefulForKey( 212 transform: suspend FrpStateScope.(A) -> B, 213 ): TFlow<Map<K, Maybe<B>>> = 214 pairwise().map { ((t0, patch), (t1, _)) -> 215 patch.map { (k, ma) -> 216 ma.map { a -> 217 FrpStateScope(t0, t1).transform(a) 218 } 219 } 220 } 221 } 222 223} 224 225``` 226