-
-
Notifications
You must be signed in to change notification settings - Fork 244
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
Observable.unfold #970
Observable.unfold #970
Conversation
monix-reactive/shared/src/main/scala/monix/reactive/internal/operators/UnfoldObservable.scala
Outdated
Show resolved
Hide resolved
monix-reactive/shared/src/main/scala/monix/reactive/internal/operators/UnfoldObservable.scala
Outdated
Show resolved
Hide resolved
|
||
import monix.execution.Ack.Continue | ||
import monix.reactive.{BaseTestSuite, Observable} | ||
object UnfoldSuite extends BaseTestSuite { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could copy few tests from StateActionObservableSuite - especially to do things like:
val cancelable = Observable
.fromStateAction(int)(s.clockMonotonic(MILLISECONDS))
.unsafeSubscribeFn(new Subscriber[Int] {
implicit val scheduler = s
def onNext(elem: Int) = {
sum += 1
Continue
}
def onComplete() = wasCompleted = true
def onError(ex: Throwable) = wasCompleted = true
})
to see if Observable
completed. I think it could fail currently. Alternatively we can call methods like sumL
or completedL
to complete it in nicer way and execute as a Task
. subscribe
just processes it asynchronously in the background
Also I'd suggest naming the suite to UnfoldObservableSuite
- we have also Iterant
and if we add unfold
for it then it can be a bit confusing to search for
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i added the test with subscribe
for now, I tried to write it like this:
test("should be cancelable") { implicit s =>
val cancelable = Observable
.unfold(s.clockMonotonic(MILLISECONDS))(intOption)
.sumL
.runToFuture
cancelable.cancel()
s.tick()
assertEquals(cancelable.value, Some(Success(s.executionModel.recommendedBatchSize * 2)))
}
but it returns None(while the subscribe
one works) - i need to dig deeper and find out why
Thank you @pk044 - great job overall! I've left some comments.
It's fine to have duplicated code, we often do it in purpose for a little gain in efficiency. I wouldn't do it in a "normal" code base but we have an excuse. :)
I think BTW It would be cool if we can come up with a property that tests |
…lled onComplete() when None is returned, added a test for checking if the function is cancelable
@Avasil thanks! i'll see what I can do about the property test :) |
@Avasil i'm not too familiar with property-based testing yet, I came up with this so far: test("unfold and fromStateAction lists should be equal given generated inputs") { implicit s =>
check {
forAll(Gen.choose(0, Platform.recommendedBatchSize * 2), Gen.choose(0, Platform.recommendedBatchSize * 2)) {
(seed, max) =>
val f: Int => Option[(Int, Int)] = i => if (i < max) Some((i, i + 1)) else None
val f2: Int => (Int, Int) = i => (i, i + 1)
Observable.unfold(seed)(f).toListL <-> Observable.fromStateAction(f2)(seed).takeWhile(_ < max).toListL
}
}
} is it what we wanted more or less? |
|
||
test("unfold and fromStateAction results should be equal given generated inputs") { implicit s => | ||
check { | ||
forAll(Gen.choose(0, Platform.recommendedBatchSize * 2), Gen.choose(0, Platform.recommendedBatchSize * 2)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the test is good but I'd recommend doing just check2
and val n = i % (recommendedBatchSize * 2)
like in many Iterant
tests, it's a bit less noise when reading tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so basically something like
check2 { (s: Int, i: Int) =>
val seed = s % (recommendedBatchSize * 2)
val n = i % (recommendedBatchSize * 2)
? the test would hang if i used the s
variable directly, probably because the generated number is way too big
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like seed
should be fine but there's no harm in doing it this way 👍
* adding unfold implementation and tests * adding documentation * formatted code * added more details to documentation, moved to appropriate package, called onComplete() when None is returned, added a test for checking if the function is cancelable * moved Unfold to builders * adding property test for unfold * extracted calls to variables
Solves #955
i have a few doubts though:
fromStateAction
. i was thinking of extracting the common parts somewhere and try to reuse them in both functions - not to mention that more unfold-based functions are planned to be implemented in Monix (unfoldEval, unfoldEvalF), is there perhaps a generic way to handle this?after dabbling with it for a little while it looks like it hangs because the size of the generated list (
seed to n toList
) might be a little too big. I think I'm supposed to have that list in the parameters, and come up with some sort of law so that ScalaCheck would always generate the expected list, instead of letting me doing it myself. (not sure if that was clear enough)do you guys perhaps have any suggestions for any of the above? thanks!