implement trampolines for flatmap, map, filter, merge. #2900
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
in this PR
fixes #2896
Context:
Arb.flatMap
,Arb.map
,Arb.filter
andArb.merge
was previously implemented using callbacks, where it creates a new arb which.edgecase
and.sample
function directly calls the receiver arb's function. While this works for shallow nestings, on bigger nestings and more complex structures this approach can easily exhaust the call stack.In this PR i implemented a simple trampoline to transform the receiver
Arb<A>
toTrampolineArb<A>
.TrampolineArb<A>
stores the initial arb and all subsequent operation chains inside a list, which will be executed in order of application. This allows us to compose a longer chain of flatMaps without blowing up the stack. Visually it looks like this:That function will then be computed by collapsing the function list from left to right and applying the random source from the context of
.sample(rs)
or.edgecase(rs)
. If one is familiar with the trampoline implementation of Free Monad, this more or less the same mechanism implemented without tail recursion and trampoline data type.Similarly within
arbitrary { }
builder, it appears that suspensions also has a limit on the number of suspension points that can be stored. Since Continuations are single-shot anyway, we can instead pass in the type of action that the call site needs, e.g. whether it is an edgecase generation or sample generation. This way we can get rid of the.resume()
call within the.bind()
in favour of just a simple return.