-
Notifications
You must be signed in to change notification settings - Fork 642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/introduce continuation arbs builder #2494
Feature/introduce continuation arbs builder #2494
Conversation
You could also do this I think ? val arb: Arb<String> = arbitrary { rs ->
val first = Arb.string(5, Codepoint.alphanumeric()).withEdgecases("edge1", "edge2").next(rs)
val second = Arb.int(1..9).withEdgecases(5).next(rs)
val third = Arb.int(101..109).withEdgecases(100 + second, 109).next(rs)
"$first $second $third"
} |
You could, the downside is one would have to be prepared to also forego the edgecases of the dependent arbs. I guess, the general question is, how might we chain arbs such that all the intrinsics are propagated fully? We look at flatmap for instance as a central point of coordination. That solves edgecases compositions without cross-contaminating samples, and potentially many other possibilities such as propagation of shrinkers and classifications. |
Yes fair.
…On Sat, 11 Sep 2021, 16:23 Mitchell Yuwono, ***@***.***> wrote:
You could also do this I think ?
val arb: Arb<String> = arbitrary { rs ->
val first = Arb.string(5, Codepoint.alphanumeric()).withEdgecases("edge1", "edge2").next(rs)
val second = Arb.int(1..9).withEdgecases(5).next(rs)
val third = Arb.int(101..109).withEdgecases(100 + second, 109).next(rs)
"$first $second $third"
}
You could, the downside is one would have to be prepared to also forego
the edgecases of the dependent arbs.
—
You are receiving this because your review was requested.
Reply to this email directly, view it on GitHub
<#2494 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAFVSGUJQ5WCRWLZYM3SHR3UBPCDZANCNFSM5D27I7NA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
|
Can you talk me through the implementation a bit. At least I'm not clear how the returnedArb gets populated in the continuation exactly. |
kotest-property/src/commonMain/kotlin/io/kotest/property/arbitrary/builders.kt
Outdated
Show resolved
Hide resolved
awesome. so i think i'd properly quote my reference in https://github.com/arrow-kt/arrow-core/blob/40619f87b7fc1c790c3ab7b7341005a960630a27/arrow-core-data/src/main/kotlin/arrow/typeclasses/Monad.kt This looks a bit weird at first, but it is actually quite simple. It's in fact very clever! so let's try this code for instance: arbitrary {
val string: String = Arb.string(5).bind()
val int: Int = Arb.int(1..100).bind()
"$string $int"
} now the sequence actually happen as follows 1. suspending computation: at line 2, i.e. when bind() is called.that executes this function override suspend fun <B> Arb<B>.bind(): B = suspendCoroutineUninterceptedOrReturn { c ->
// we call flatMap on the bound arb, and then returning the `returnedArb`, without modification
returnedArb = this.flatMap { b: B ->
// we resume the suspension with the value passed inside the flatMap function.
// this can be either sample or edgecases. This is important
// because from the point of view of a user of kotest, when we talk about transformation,
// we care about transforming the generated value of this arb for both sample and edgecases.
c.resume(b)
returnedArb // returned arb here is returned as is
}
COROUTINE_SUSPENDED
} Notice this block returns the special override fun resumeWith(result: Result<Arb<A>>) {
result.map { theNewArb -> returnedArb = theNewArb }.getOrThrow()
} if you realize, this // continuation is a single shot so this needs to be a function to reinstate value randomization through the arbs
private fun computeArb(): Arb<A> {
val continuation = ArbContinuation<A>()
// this is where the point action happen, i.e. when we build the final arb from the returned value from the block.
// essentially at the end of the suspend we got `A`, and here we need to point it into `Arb<A>`
val wrapReturn: suspend ArbitraryBuilderSyntax.() -> Arb<A> = {
// the value after finishing our builder block
val value: A = builderFn(randomSource.bind())
// we then create a new arb here with that value.
ArbitraryBuilder(
{ value },
classifier,
shrinker,
{ rs -> edgecaseFn?.invoke(rs) ?: value }
).build()
}
// all the functions are set, we start our coroutine, providing our continuation instance, this will run the suspend block
wrapReturn.startCoroutine(continuation, continuation)
// once that is finished, the `continuation.resumeWith` will be called, where we assign the `returnedArb` var.
// after that, we then return that populated value.
return continuation.returnedArb()
} |
I'll pop those comments in the code as well |
Can we add the generateArbitrary variant which allows suspension in the lamba (inline?) |
kotest-property/src/commonMain/kotlin/io/kotest/property/arbitrary/builders.kt
Outdated
Show resolved
Hide resolved
added |
This is a proposed implementation for arbitrary builders using continuation passing style. This code is inspired by how monad fx on any
Kind<F, A>
was previously done in arrow.This allows the following simplification as described in #2493:
fixes #2493