I believe this code works correctly. In that case,...
# kotest
t
I believe this code works correctly. In that case, maybe it's something to add in the lib? If there is a better way, or if there is a bug, which one?
Copy code
open class FlatmapCombinedShrinker2<Original, value1, value2>(
    private val select1: (Original) -> value1,
    private val shrinker1: Shrinker<value1>,
    private val select2: (Original) -> value2,
    private val shrinker2: Shrinker<value2>,
    private val composer: Original.(value1, value2) -> Original
) : Shrinker<Original> {
    override fun shrink(value: Original): List<Original> {
        val hasShrinks = BooleanArray(2) { true }

        val value1 = select1(value)
        val shrinks1 = shrinker1.shrink(value1).ifEmpty {
            hasShrinks[0] = false
            listOf(value1)
        }

        val value2 = select2(value)
        val shrinks2 = shrinker2.shrink(value2).ifEmpty {
            hasShrinks[1] = false
            listOf(value2)
        }

        return if (hasShrinks.any { it }) {
            shrinks1.flatMap { val1 ->
                shrinks2.map { val2 ->
                    value.composer(val1, val2)
                }
            }
        } else emptyList()
    }
}
s
What's the bug
t
I have never encountered a bug with this one. I also never have written explicit tests for it as it seems to work most of the time... the only "bug" is that it creates a lots of values, but that can be removed. I just didn't want to claim it was bugfree 😉 I believe the code below has a bug though:
Copy code
class NestedListShrinker<T>(
    private val range: IntRange,
    val shrinker: Shrinker<T>
) : Shrinker<List<T>> {

    val shrinkMap = mutableMapOf<T, List<T>>()
    private fun T.getShrinks() = shrinkMap.getOrPut(this){
        shrinker.shrink(this)
    }

    override fun shrink(value: List<T>): List<List<T>> {
        return ListShrinker<T>(range).shrink(value).mapNotNull { list ->
            var shrinkFound  = false
            val childShrinks = list.map { t ->
                val shrinks = t.getShrinks()
                if (shrinks.isNotEmpty()){
                    shrinkFound = true
                    shrinks
                } else listOf(t)
            }
            if (shrinkFound) {
                childShrinks.crossProduct { outer, inner ->
                    outer + inner
                }
            } else null
        }.flatten().distinct()
    }
}
s
just you said If there is a better way, or if there is a bug, which one?
I don't understand what you're asking here. Are you proposing to add this implementation for shrinkers ?
t
If I understand correctly, bind isn't doing the above autimatically (when using a lot of fields, that probably would become expensive real quick). I basically want to ask if using code like above is a correct usage of the framework, or if there is something better. If it is correct usage, can it provide some value to the framework?
s
Right I see, yes if it works, and you can add tests around it, adding it to bind would be great.
t
the bind-file you mean? It can be added to bind, but I think it would ignore the fact that it is generating loads of values. In my FlatmapCombine it therefor provides the original value in the last call, such that it doesn't shrink all the fields. Is adding it straight to bind the best approach?
s
I think it's ok to add it to bind if that leads to a better solution
t
Ok, I will work on it on saturday
s
awesome
t
Wait, just remembered: it's not possible as you don't have access to shrinkers.
Copy code
fun <T> Arb<T>.shrinkWith(
    shrinker: Shrinker<T>
) = arbitrary(edgecases(), shrinker) { next(it) }


infix fun <Original, T> KProperty1<Original, T>.shrinkWith(
    shrinker: Shrinker<T>
) = PartialShrinker<Original, T>(
    { this.get(it) },
    shrinker
)

infix fun <Original, V> ShrinkerSelectPart<Original, V>.shrinkWith(
    shrinker: Shrinker<V>
) = PartialShrinker(
    selection,
    shrinker
)

class ShrinkerSelectPart<Original, V>(
    val selection: (Original) -> V
)


fun <Original, A> ShrinkerBuilder<Original>.build(
    partialShrinkerA: PartialShrinker<Original, A>,
    completeFn: Original.(A) -> Original
): Shrinker<Original> =
    partialShrinkerA.build(completeFn)



fun <Original, A, B> ShrinkerBuilder<Original>.build(
    partialShrinkerA: PartialShrinker<Original, A>,
    partialShrinkerB: PartialShrinker<Original, B>,
    completeFn: Original.(A, B) -> Original
): Shrinker<Original> =
    partialShrinkerA.bind(partialShrinkerB).build { (a, b) ->
        completeFn(a, b)
    }


fun <Original, A, B, C> ShrinkerBuilder<Original>.build(
    partialShrinkerA: PartialShrinker<Original, A>,
    partialShrinkerB: PartialShrinker<Original, B>,
    partialShrinkerC: PartialShrinker<Original, C>,
    completeFn: Original.(A, B, C) -> Original
): Shrinker<Original> = partialShrinkerA.bind(partialShrinkerB).bind(partialShrinkerC)
    .build { (ab, c) ->
        val (a, b) = ab
        completeFn(a, b, c)
    }


fun <Original, A, B, C, D> ShrinkerBuilder<Original>.build(
    partialShrinkerA: PartialShrinker<Original, A>,
    partialShrinkerB: PartialShrinker<Original, B>,
    partialShrinkerC: PartialShrinker<Original, C>,
    partialShrinkerD: PartialShrinker<Original, D>,
    completeFn: Original.(A, B, C, D) -> Original
): Shrinker<Original> = partialShrinkerA.bind(partialShrinkerB).bind(partialShrinkerC)
    .bind(partialShrinkerD).build { (abc, d) ->
        val (ab, c) = abc
        val (a, b) = ab
        completeFn(a, b, c, d)
    }

fun <Original, A, B, C, D, E> ShrinkerBuilder<Original>.build(
    partialShrinkerA: PartialShrinker<Original, A>,
    partialShrinkerB: PartialShrinker<Original, B>,
    partialShrinkerC: PartialShrinker<Original, C>,
    partialShrinkerD: PartialShrinker<Original, D>,
    partialShrinkerE: PartialShrinker<Original, E>,
    completeFn: Original.(A, B, C, D, E) -> Original
): Shrinker<Original> =
    partialShrinkerA.bind(partialShrinkerB).bind(partialShrinkerC).bind(partialShrinkerD)
        .bind(partialShrinkerE).build { (abcd, e) ->
            val (abc, d) = abcd
            val (ab, c) = abc
            val (a, b) = ab
            this.completeFn(a, b, c, d, e)
        }

fun <Original, A, B, C, D, E, F> ShrinkerBuilder<Original>.build(
    partialShrinkerA: PartialShrinker<Original, A>,
    partialShrinkerB: PartialShrinker<Original, B>,
    partialShrinkerC: PartialShrinker<Original, C>,
    partialShrinkerD: PartialShrinker<Original, D>,
    partialShrinkerE: PartialShrinker<Original, E>,
    partialShrinkerF: PartialShrinker<Original, F>,
    completeFn: Original.(A, B, C, D, E, F) -> Original
): Shrinker<Original> =
    partialShrinkerA.bind(partialShrinkerB).bind(partialShrinkerC).bind(partialShrinkerD)
        .bind(partialShrinkerE).bind(partialShrinkerF).build { (abcde, f) ->
            val (abcd, e) = abcde
            val (abc, d) = abcd
            val (ab, c) = abc
            val (a, b) = ab
            completeFn(a, b, c, d, e, f)
        }

fun <Original, A, B, C, D, E, F, G> ShrinkerBuilder<Original>.build(
    partialShrinkerA: PartialShrinker<Original, A>,
    partialShrinkerB: PartialShrinker<Original, B>,
    partialShrinkerC: PartialShrinker<Original, C>,
    partialShrinkerD: PartialShrinker<Original, D>,
    partialShrinkerE: PartialShrinker<Original, E>,
    partialShrinkerF: PartialShrinker<Original, F>,
    partialShrinkerG: PartialShrinker<Original, G>,
    completeFn: Original.(A, B, C, D, E, F, G) -> Original
): Shrinker<Original> = partialShrinkerA.bind(partialShrinkerB).bind(partialShrinkerC)
    .bind(partialShrinkerD).bind(partialShrinkerE).bind(partialShrinkerF)
    .bind(partialShrinkerG).build { (abcdef, g) ->
        val (abcde, f) = abcdef
        val (abcd, e) = abcde
        val (abc, d) = abcd
        val (ab, c) = abc
        val (a, b) = ab
        completeFn(a, b, c, d, e, f, g)
    }

fun <Original, A, B, C, D, E, F, G, H, T> ShrinkerBuilder<Original>.build(
    partialShrinkerA: PartialShrinker<Original, A>,
    partialShrinkerB: PartialShrinker<Original, B>,
    partialShrinkerC: PartialShrinker<Original, C>,
    partialShrinkerD: PartialShrinker<Original, D>,
    partialShrinkerE: PartialShrinker<Original, E>,
    partialShrinkerF: PartialShrinker<Original, F>,
    partialShrinkerG: PartialShrinker<Original, G>,
    partialShrinkerH: PartialShrinker<Original, H>,
    completeFn: Original.(A, B, C, D, E, F, G, H) -> Original
): Shrinker<Original> = partialShrinkerA.bind(partialShrinkerB).bind(partialShrinkerC)
    .bind(partialShrinkerD).bind(partialShrinkerE).bind(partialShrinkerF)
    .bind(partialShrinkerG).bind(partialShrinkerH)
    .build { (abcdefg, h) ->
        val (abcdef, g) = abcdefg
        val (abcde, f) = abcdef
        val (abcd, e) = abcde
        val (abc, d) = abcd
        val (ab, c) = abc
        val (a, b) = ab
        completeFn(a, b, c, d, e, f, g, h)
    }

fun <Original, A, B, C, D, E, F, G, H, I> ShrinkerBuilder<Original>.build(
    partialShrinkerA: PartialShrinker<Original, A>,
    partialShrinkerB: PartialShrinker<Original, B>,
    partialShrinkerC: PartialShrinker<Original, C>,
    partialShrinkerD: PartialShrinker<Original, D>,
    partialShrinkerE: PartialShrinker<Original, E>,
    partialShrinkerF: PartialShrinker<Original, F>,
    partialShrinkerG: PartialShrinker<Original, G>,
    partialShrinkerH: PartialShrinker<Original, H>,
    partialShrinkerI: PartialShrinker<Original, I>,
    completeFn: Original.(A, B, C, D, E, F, G, H, I) -> Original
): Shrinker<Original> = partialShrinkerA.bind(partialShrinkerB).bind(partialShrinkerC)
    .bind(partialShrinkerD).bind(partialShrinkerE).bind(partialShrinkerF)
    .bind(partialShrinkerG).bind(partialShrinkerH).bind(partialShrinkerI)
    .build { (abcdefgh, i) ->
        val (abcdefg, h) = abcdefgh
        val (abcdef, g) = abcdefg
        val (abcde, f) = abcdef
        val (abcd, e) = abcde
        val (abc, d) = abcd
        val (ab, c) = abc
        val (a, b) = ab
        completeFn(a, b, c, d, e, f, g, h, i)
    }

fun <Original, A, B, C, D, E, F, G, H, I, J> ShrinkerBuilder<Original>.build(
    partialShrinkerA: PartialShrinker<Original, A>,
    partialShrinkerB: PartialShrinker<Original, B>,
    partialShrinkerC: PartialShrinker<Original, C>,
    partialShrinkerD: PartialShrinker<Original, D>,
    partialShrinkerE: PartialShrinker<Original, E>,
    partialShrinkerF: PartialShrinker<Original, F>,
    partialShrinkerG: PartialShrinker<Original, G>,
    partialShrinkerH: PartialShrinker<Original, H>,
    partialShrinkerI: PartialShrinker<Original, I>,
    partialShrinkerJ: PartialShrinker<Original, J>,
    completeFn: Original.(A, B, C, D, E, F, G, H, I, J) -> Original
): Shrinker<Original> = partialShrinkerA
    .bind(partialShrinkerB)
    .bind(partialShrinkerC)
    .bind(partialShrinkerD)
    .bind(partialShrinkerE)
    .bind(partialShrinkerF)
    .bind(partialShrinkerG)
    .bind(partialShrinkerH)
    .bind(partialShrinkerI)
    .bind(partialShrinkerJ)
    .build { (abcdefghi, j) ->
        val (abcdefgh, i) = abcdefghi
        val (abcdefg, h) = abcdefgh
        val (abcdef, g) = abcdefg
        val (abcde, f) = abcdef
        val (abcd, e) = abcde
        val (abc, d) = abcd
        val (ab, c) = abc
        val (a, b) = ab
        completeFn(a, b, c, d, e, f, g, h, i, j)
    }

fun <Original, Value> PartialShrinker<Original, Value>.build(
    shrinkFn: Original.(Value) -> Original
) = object : Shrinker<Original> {
    override fun shrink(
        value: Original
    ): List<Original> {
        return shrinker
            .shrink(selection(value))
            .map { shrinkFn(value, it) }
    }
}

fun <Original, A, B> ShrinkerBuilder<Original>.bind(
    selectionA: PartialShrinker<Original, A>,
    selectionB: PartialShrinker<Original, B>,
) = selectionA.bind(selectionB)

fun <Original, A, B> PartialShrinker<Original, A>.bind(
    other: PartialShrinker<Original, B>
) = PartialShrinker<Original, Pair<A, B>>(
    { this.selection(it) to other.selection(it) },
    FlatmapShrinker(this.shrinker, other.shrinker)
)


class PartialShrinker<Original, V>(
    val selection: (Original) -> V,
    val shrinker: Shrinker<V>
) {
    companion object
}

fun <T> Shrinker<T>.withCache() = when (this) {
    is ShrinkerWithCache -> this
    else -> ShrinkerWithCache(this)
}

open class ShrinkerWithCache<T>(
    private val shrinker: Shrinker<T>
) : Shrinker<T> {
    val shrinks = mutableMapOf<T, List<T>>()
    override fun shrink(
        value: T
    ) = shrinks.getOrPut(
        value
    ) { shrinker.shrink(value) }
}

open class FlatmapShrinker<value1, value2>(
    shrinker1: Shrinker<value1>,
    shrinker2: Shrinker<value2>,
) : Shrinker<Pair<value1, value2>> {
    val shrinker1 = shrinker1.withCache()
    val shrinker2 = shrinker2.withCache()
    override fun shrink(value: Pair<value1, value2>): List<Pair<value1, value2>> {
        val shrinks1 = shrinker1.shrink(value.first)
        val shrinks2 = shrinker2.shrink(value.second)

        if (shrinks1.isEmpty() && shrinks2.isEmpty()) return emptyList()

        val nonEmptyShrinks1 = shrinks1.ifEmpty { listOf(value.first) }
        val nonEmptyShrinks2 = shrinks2.ifEmpty { listOf(value.second) }
        return nonEmptyShrinks1.flatMap { value1 ->
            nonEmptyShrinks2.map { value2 ->
                value1 to value2
            }
        }
    }
}


class ShrinkerBuilder<Original> {
    fun <T> select(
        lambda: Original.() -> T
    ) = ShrinkerSelectPart(lambda)

    infix fun <T> ShrinkerSelectPart<Original, T>.shrinkWith(
        shrinker: Shrinker<T>
    ) = PartialShrinker(selection, shrinker)
}

fun <Original> createShrinker(
    shrinkerBuilder: ShrinkerBuilder<Original>.() -> Shrinker<Original>
) = ShrinkerBuilder<Original>().shrinkerBuilder()
Copy code
val OrganisationShrinker = createShrinker<Organisation> {
    build(
        select { organisationID.value } shrinkWith StringShrinker,
        Organisation::name shrinkWith StringShrinker,
        Organisation::type shrinkWith StringShrinker,
        Organisation::thumbnail shrinkWith StringShrinker,
        Organisation::clientId shrinkWith IntShrinker(0..100)
    ) { orgId, name, type, thumb, clientId ->
        copy(
            OrganisationID(orgId),
            name,
            type,
            clientId,
            thumb,
            baseUrl,
            primaryColor
        )
    }
}
this can be achieved with the following code:
s
wanna make a PR ?
t
didn't create tests yet...
s
ok
If you make I'll a PR later with tests I will give more detailed feedback on github
👍🏻 1
t
Added the PR and updated "ListShrinker in action"