Closed bbarker closed 6 years ago
At first blush it looks like dropRepeats isn't working properly. noting that System.identityHashCode(capiUri) will return different values for different objects even if the objects are equal... If you re-ran adding capiUri.hashCode instead I suspect you would get the same hashcode. Noting this is similar dropRepeats issue I have occasionally seen but never been able to pin down.
@bbarker Could this be solved by using an equality type class (cats.Eq
) instead of ==
in dropRepeats
? IIUC you could work around the issue by warping your problematic object into a class implementing whatever equality you need:
case class UriEq(value: URI) {
override def equals(other: Object): Boolean = value.toString == other.toString
}
val rx: Rx[URI] = ...
rx.map(UriEq.apply).dropRepeats.map(_.value)
@OlivierBlanvillain That would be nice for some things (very concise!), but I don't understand how it would help here: it appears ==
works as expected on java.net.URI
(at least in the console example shown above).
@fizzy33 right, I wanted to use the identity hash to see if new objects were actually being created. it is interesting to note that in the second example, even though we have the same identity hash, I see multiple firings.
@bbarker Indeed it looks like the issue is not on equality itself, I read your text a bit too fast ^^'
I would be very surprised if the problem came from dropRepeats
directly, the implementation is so simple... My best guess is that currentApiUri
is used in the right hand side of a flatMap
(the following does not hold: ra.flatMap(_ => rb.dropRepeats) =!= ra.flatMap(_ => rb).dropRepeats
).
I think I would need a minimized version to get to the bottom of this.
To be more explicit. Are we sure that URI.equals() isn't broken on the js platform?
Good suspicion, equality looks non trivial indeed
@fizzy33 I admit I was wondering about that, but at least in this simple case ==
works as expected in scala.js: https://scalafiddle.io/sf/nnfr0VI/0 , but I'll investigate at bit more along those lines.
@OlivierBlanvillain Yeah, I also doubt it is dropRepeats
causing the problem ... I looked at the simple implementation and thought the same. However, I do think it would be nice to have a dropRepeats
that can inspect its current value using a specified "keep" function.
It makes sense to me that you'd want to dropRepeates
after a flatMap
, and not inside of flatMap
, but don't see anything obvious popping up here (the only flatMap
in my code is the one you see) - I'll see if I can make a minified example
I found the (indirect) cause for redundancies using the second method in the original post (or at least most of the redundancies); I have several use cases along these lines that simply use Rx.map
:
implicit val port: Rx[Port] = currentApiUri.map{ curUri =>
Option(curUri.getPort) match {
case Some(prt) => if (prt < 0) Port(80) else Port(prt)
case None => Port(defaultPort)
}
}
When I remove this, the log count went from 9 to 7 in my app.
So it seems just mapping again can cause an additional execution in the upstream Rx (currentApiUri
), though my impression is that this value should be memoized in some form in the upstream Rx.
So it seems just mapping again can cause an additional execution in the upstream Rx (currentApiUri), though my impression is that this value should be memoized in some form in the upstream Rx.
This is working as intended, there is no memoization implemented in the library whatsoever:
val source: Var[Int] = Var(0)
def printing[X](x: X): X = {
println(x)
x
}
val mapped = source.map(x => printing(x + 1)).map(x => printing(x + 2))
val cc1 = mapped.impure.foreach(_ => ())
val cc2 = mapped.impure.foreach(_ => ())
// 1
// 3
// 1
// 3
source := 1
// 2
// 4
// 2
// 4
source := 2
// 3
// 5
// 3
// 5
source := 3
// 4
// 6
// 4
// 6
cc1.cancel
cc2.cancel
If you add a 3rd mapped.impure.foreach(_ => ())
, then you would get a 3rd version of every line...
@bbarker it was one of those check the obvious things first, I am fairly convinced it isn't that.
fwiw I have been trying repro as well with no success yet.
For the sake of completeness here is a simple test, though i guess we've probably beaten this one to death by now:
var count = 0
val sourceVar = Var(0)
val source: Rx[Int] = sourceVar.map{x => count +=1; x}
val noshare1: Rx[Int] = source.map(identity)
val noshare2: Rx[Int] = source.map(identity)
val cc_ns1 = noshare1.impure.foreach(_ => ())
val cc_ns2 = noshare2.impure.foreach(_ => ())
assert(count == 2)
sourceVar := 1
assert(count == 4)
cc_ns1.cancel
cc_ns2.cancel
I think the point of good tests is to beat things to death :-)
Actually, I may have spoke too soon (at least for myself) - I thought I ran the current form but I'm getting the second assert failing with count == 6
, not count == 4
.
fwiw I ran the @bbarker test and it completed without error i.e. both asserts passed. Running in chrome.
@fizzy33 I realized my issue; I had added some asserts that use impure.value
- these run foreach
behind the scenes.
This is more of a discussion rather than a direct issue, but as there is a fair amount to say I bring it up here instead of gitter.
The basic idea is that
Rx.dropRepeat
is fine for values but not fine for objects in general.The problem might be avoided with a call to
Rx.foldp
; here is a real example that isn't (too) long:The problem with
foldp
here is thatcurUriObj
doesn't come from the current value ofcurrentApiUri
itself, but rather from its input sources:In our case, since
checkApiUri
creates and returns a newURI
object every time it is called,foldp
always updates. A relevant part of the web console looks like this for a particular route:An alternative is to define a version of
dropRepeats
that maintains an internalVar
so thatVar.update
can be called. However, this ends up being a bit tricky, but perhaps it can be simplified (especially if internalized to monadic-rx, I suspect):In particular, I'm dubious about my use of
finalize
, but I hope that at least demonstrats what I hope is happening...I'm also a bit surprised that, in this case, the different
URI
objects apparently evaluate as being unequal incase DropRep(self) =>
inrx.scala
, though maybe this isn't what is happening. Nonetheless,Rx.dropRepeats
is apparently not sufficient to prevent multiple emissions with the same URI (capiUri
) but different hashcodes in the first example/println
above.When we test URI equivlance we get what we expect in the Scala repl:
The equivalent web console output for our new
dropRepeat
function is :However, this happens 9 times for the same route, and is even more surprising since it is being executed 9 times, even though each URI has the same URI string and hash code. In my code, all these definitions are in a top-level object, so I don't see multiple instance creation being a factor in causing repeated identical messages.
I'm at a bit of a loss. If nothing obvious comes up, I can try to convert some of this into unit test to see if that may help identifying (or at least sharing) the issue.