Skip to content

Commit e2605d2

Browse files
committed
Port state tests and add StatefulHandler
1 parent 338143f commit e2605d2

File tree

7 files changed

+296
-104
lines changed

7 files changed

+296
-104
lines changed

library/src/commonMain/kotlin/effekt/handler.kt

+37-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package effekt
22

33
import Prompt
44
import abortS0
5+
import ask
6+
import context
57
import pushPrompt
68
import reset
79
import shift0
@@ -11,13 +13,24 @@ import kotlin.jvm.JvmInline
1113

1214
public interface Handler<E> {
1315
public fun prompt(): HandlerPrompt<E>
16+
public suspend fun <T> Reader<T>.get(): T = ask()
17+
public suspend fun <T> Reader<T>.set(value: T): Unit = useWithContext { k, _ ->
18+
k(Unit, context(value))
19+
}
1420
}
1521

22+
public interface StatefulHandler<E, S> : Handler<E>, Reader<S>
23+
24+
public suspend fun <A, E, S> StatefulHandler<E, S>.useStateful(body: suspend (suspend (A, S) -> E, S) -> E): A =
25+
useWithContext { k, ctx ->
26+
body({ a, s -> k(a, context(s)) }, ctx[this]!!.value)
27+
}
28+
1629
public suspend fun <A, E> Handler<E>.use(body: suspend (Cont<A, E>) -> E): A = prompt().prompt.shift0(body)
1730
public fun <E> Handler<E>.discard(body: suspend () -> E): Nothing = prompt().prompt.abortS0(body)
18-
public suspend fun <A, E> Handler<E>.useStateful(body: suspend (suspend (A, CoroutineContext) -> E) -> E): A =
31+
public suspend fun <A, E> Handler<E>.useWithContext(body: suspend (suspend (A, CoroutineContext) -> E, CoroutineContext) -> E): A =
1932
prompt().prompt.takeSubCont { sk ->
20-
body { a, ctx -> sk.pushSubContWith(Result.success(a), isDelimiting = true, extraContext = ctx) }
33+
body({ a, ctx -> sk.pushSubContWith(Result.success(a), isDelimiting = true, extraContext = ctx) }, sk.extraContext)
2134
}
2235

2336
public suspend fun <E, H> handle(
@@ -28,18 +41,32 @@ public suspend fun <E> handle(body: suspend HandlerPrompt<E>.() -> E): E = with(
2841
handle { body() }
2942
}
3043

31-
public suspend fun <E> HandlerPrompt<E>.handle(body: suspend () -> E): E = prompt.reset(body)
44+
public suspend fun <E> Handler<E>.handle(body: suspend () -> E): E = prompt().prompt.reset(body)
3245

33-
public suspend fun <E, H> handleStateful(
46+
public suspend fun <E, H> handleWithContext(
3447
handler: ((() -> HandlerPrompt<E>) -> H), extraContext: CoroutineContext, body: suspend H.() -> E
35-
): E = handleStateful(extraContext) { handler { this }.body() }
48+
): E = handleWithContext(extraContext) { handler { this }.body() }
3649

37-
public suspend fun <E> handleStateful(extraContext: CoroutineContext, body: suspend HandlerPrompt<E>.() -> E): E =
38-
HandlerPrompt<E>().handleStateful(extraContext, body)
50+
public suspend fun <E> handleWithContext(extraContext: CoroutineContext, body: suspend HandlerPrompt<E>.() -> E): E =
51+
with(HandlerPrompt<E>()) {
52+
handleWithContext(extraContext) { body() }
53+
}
54+
55+
public suspend fun <E> Handler<E>.handleWithContext(
56+
extraContext: CoroutineContext, body: suspend () -> E
57+
): E = prompt().prompt.pushPrompt(extraContext, body = body)
58+
59+
public suspend fun <E, H : StatefulHandler<E, S>, S> handleStateful(
60+
handler: ((() -> HandlerPrompt<E>) -> H), value: S, body: suspend H.() -> E
61+
): E {
62+
val p = HandlerPrompt<E>()
63+
val h = handler { p }
64+
return h.handleStateful(value) { h.body() }
65+
}
3966

40-
public suspend fun <E> HandlerPrompt<E>.handleStateful(
41-
extraContext: CoroutineContext, body: suspend HandlerPrompt<E>.() -> E
42-
): E = prompt.pushPrompt(extraContext) { body() }
67+
public suspend fun <E, S> StatefulHandler<E, S>.handleStateful(
68+
value: S, body: suspend () -> E
69+
): E = handleWithContext(context(value), body)
4370

4471
@JvmInline
4572
public value class HandlerPrompt<E> private constructor(internal val prompt: Prompt<E>) : Handler<E> {

library/src/commonMain/kotlin/effekt/state.kt

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package effekt
22

3-
import get
4-
import newReset
53
import pushState
4+
import get
65
import set
7-
import shift0
86

97
public interface StateScope {
108
public suspend fun <T> field(init: T): Field<T>
@@ -18,13 +16,13 @@ public suspend inline fun <T> StateScope.Field<T>.update(f: (T) -> T) {
1816
set(f(get()))
1917
}
2018

21-
public suspend fun <R> region(body: suspend StateScope.() -> R): R = newReset {
19+
public suspend fun <R> region(body: suspend StateScope.() -> R): R = handle {
2220
body(StateScopeImpl(this))
2321
}
2422

25-
private class StateScopeImpl<R>(val prompt: Prompt<R>) : StateScope {
23+
private class StateScopeImpl<R>(prompt: HandlerPrompt<R>) : StateScope, Handler<R> by prompt {
2624
override suspend fun <T> field(init: T): StateScope.Field<T> = FieldImpl<T>().apply {
27-
prompt.shift0 {
25+
use {
2826
pushState(init) {
2927
it(Unit)
3028
}

library/src/commonTest/kotlin/effekt/PaperTest.kt

+15-29
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
package effekt
22

3-
import Reader
43
import arrow.core.None
54
import arrow.core.Option
65
import arrow.core.Some
7-
import ask
8-
import context
96
import io.kotest.matchers.shouldBe
107
import kotlinx.coroutines.test.runTest
118
import runCC
129
import kotlin.collections.plus
13-
import kotlin.coroutines.CoroutineContext
1410
import kotlin.test.Test
1511

1612
class PaperTest {
@@ -198,42 +194,32 @@ suspend fun <R> firstResult(block: suspend NonDetermined.() -> R): Option<R> = h
198194
Some(block())
199195
}
200196

201-
interface Scheduler : Fiber, Handler<Unit> {
202-
val queue: Reader<Queue>
197+
fun interface Scheduler : Fiber, StatefulHandler<Unit, Queue> {
203198
override suspend fun exit(): Nothing = discard {}
204-
override suspend fun fork(): Boolean {
205-
val q = queue.ask()
206-
return useStateful { resume ->
207-
run(listOf<suspend (CoroutineContext) -> Unit>({ resume(true, it) }, { resume(false, it) }) + q)
208-
}
199+
override suspend fun fork(): Boolean = useStateful { resume, queue ->
200+
run(listOf(QueueItem { resume(true, it) }, QueueItem { resume(false, it) }) + queue)
209201
}
210202

211-
override suspend fun suspend() {
212-
val q = queue.ask()
213-
useStateful { resume ->
214-
run(q + { resume(Unit, it) })
215-
}
203+
override suspend fun suspend() = useStateful { resume, queue ->
204+
run(queue + QueueItem { resume(Unit, it) })
216205
}
217206
}
218207

219-
suspend fun scheduler(block: suspend Fiber.() -> Unit) {
220-
val queue = Reader<Queue>()
221-
handleStateful<Unit, Fiber>({
222-
object : Scheduler, Handler<Unit> by it() {
223-
override val queue = queue
224-
}
225-
}, queue.context(emptyList()), block)
226-
}
208+
suspend fun scheduler(block: suspend Fiber.() -> Unit) = handleStateful(::Scheduler, emptyList(), block)
227209

228-
private suspend fun Scheduler.run(q: Queue) {
210+
private suspend fun run(q: Queue) {
229211
if (q.isNotEmpty()) {
230-
val p = q.first()
231-
val newQ = q.drop(1)
232-
p(queue.context(newQ))
212+
q.first()(q.drop(1))
233213
}
234214
}
235215

236-
typealias Queue = List<suspend (CoroutineContext) -> Unit>
216+
// Needed because recursive type
217+
// Queue = List<suspend (Queue) -> Unit>
218+
fun interface QueueItem {
219+
suspend operator fun invoke(queue: Queue)
220+
}
221+
222+
typealias Queue = List<QueueItem>
237223

238224
interface Input {
239225
suspend fun read(): Char

library/src/commonTest/kotlin/effekt/ParsersTest.kt

+17-23
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package effekt
22

3-
import Reader
43
import arrow.core.None
54
import arrow.core.Option
65
import arrow.core.Some
76
import arrow.core.recover
8-
import ask
9-
import context
107
import io.kotest.matchers.shouldBe
118
import runTestCC
129
import kotlin.test.Test
@@ -145,12 +142,12 @@ interface Parser3<S> {
145142
suspend fun <A> nonterminal(name: String, body: suspend () -> A): A
146143

147144
companion object {
148-
suspend fun <A> parse(input: String, parser: suspend CharParsers.() -> A): Option<A> {
149-
val pos = Reader<Int>()
150-
return handleStateful(pos.context(0)) {
151-
Some(StringParser<A>(input, this, pos).parser())
145+
suspend fun <A> parse(input: String, parser: suspend CharParsers.() -> A): Option<A> =
146+
with(StringParser<A>(input, HandlerPrompt())) {
147+
return handleStateful(0) {
148+
Some(parser())
149+
}
152150
}
153-
}
154151
}
155152
}
156153

@@ -168,24 +165,20 @@ suspend fun Parser3<*>.alternatives(n: Int): Int {
168165
return 0
169166
}
170167

171-
class StringParser<R>(val input: String, prompt: HandlerPrompt<Option<R>>, private val pos: Reader<Int>) :
172-
CharParsers, Handler<Option<R>> by prompt {
168+
class StringParser<R>(val input: String, prompt: HandlerPrompt<Option<R>>) : CharParsers, Handler<Option<R>> by prompt,
169+
StatefulHandler<Option<R>, Int> {
173170
private val cache = mutableMapOf<Pair<Int, String>, Pair<Int, Any?>>()
174171

175172
override suspend fun any(): Char {
176-
val p = pos.ask()
173+
val p = get()
177174
if (p >= input.length) fail("Unexpected EOS")
178-
return useStateful {
179-
it(input[p], pos.context(p + 1))
180-
}
175+
set(p + 1)
176+
return input[p]
181177
}
182178

183-
override suspend fun alternative(): Boolean {
184-
val before = pos.ask()
185-
return useStateful { k ->
186-
// does this lead to left biased choice?
187-
k(true, pos.context(before)).recover { k(false, pos.context(before)).bind() }
188-
}
179+
override suspend fun alternative(): Boolean = useStateful { k, before ->
180+
// does this lead to left biased choice?
181+
k(true, before).recover { k(false, before).bind() }
189182
}
190183

191184
override suspend fun fail(explanation: String): Nothing {
@@ -194,12 +187,13 @@ class StringParser<R>(val input: String, prompt: HandlerPrompt<Option<R>>, priva
194187

195188
override suspend fun <A> nonterminal(name: String, body: suspend () -> A): A {
196189
// We could as well use body.getClass().getCanonicalName() as key.
197-
val key = Pair(pos.ask(), name)
190+
val key = Pair(get(), name)
198191
val (p, res) = cache.getOrPut(key) {
199192
val res = body()
200-
pos.ask() to res
193+
get() to res
201194
}
202-
@Suppress("UNCHECKED_CAST") return useStateful { it(res as A, pos.context(p)) }
195+
set(p)
196+
@Suppress("UNCHECKED_CAST") return res as A
203197
}
204198

205199
}

library/src/commonTest/kotlin/effekt/ProbabilisticTest.kt

+10-20
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package effekt
22

3-
import Reader
4-
import ask
5-
import context
63
import io.kotest.matchers.ranges.shouldBeIn
74
import io.kotest.matchers.shouldBe
85
import runTestCC
@@ -38,38 +35,31 @@ class ProbabilisticTest {
3835

3936
@Test
4037
fun falsePositive() = runTestCC {
41-
probabilistic {
38+
probabilistic {
4239
falsePositive()
4340
} shouldBe listOf(
44-
Weighted(false, 0.099),
45-
Weighted(true, 0.0099)
41+
Weighted(false, 0.099), Weighted(true, 0.0099)
4642
)
4743
}
4844
}
4945

50-
class ProbHandler<R>(val weight: Reader<Double>, prompt: HandlerPrompt<List<Weighted<R>>>) : Prob,
51-
Handler<List<Weighted<R>>> by prompt {
46+
class ProbHandler<R>(prompt: HandlerPrompt<List<Weighted<R>>>) : Prob, Handler<List<Weighted<R>>> by prompt,
47+
StatefulHandler<List<Weighted<R>>, Double> {
5248
override suspend fun flip(): Boolean = use { k ->
5349
k(false) + k(true)
5450
}
5551

5652
override suspend fun fail(): Nothing = discard { emptyList() }
5753

58-
override suspend fun factor(p: Double) {
59-
val w = weight.ask()
60-
useStateful {
61-
it(Unit, weight.context(p * w))
62-
}
63-
}
54+
override suspend fun factor(p: Double) = set(p * get())
6455
}
6556

66-
suspend fun <R> probabilistic(body: suspend ProbHandler<R>.() -> R): List<Weighted<R>> {
67-
val weight = Reader<Double>()
68-
return handleStateful(weight.context(1.0)) {
69-
val res = ProbHandler(weight, this).body()
70-
listOf(Weighted(res, weight.ask()))
57+
suspend fun <R> probabilistic(body: suspend ProbHandler<R>.() -> R): List<Weighted<R>> =
58+
with(ProbHandler<R>(HandlerPrompt())) {
59+
handleStateful(1.0) {
60+
listOf(Weighted(body(), get()))
61+
}
7162
}
72-
}
7363

7464
data class Weighted<T>(val value: T, val weight: Double)
7565

0 commit comments

Comments
 (0)