Structural `dropRepeat` mechanism and unexpected repeats
See original GitHub issueThis 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:
val currentApiUri: Rx[URI] = (checkApiUri(localApiUri)
|@| apiUriApp.app._2.flatMap(uri => checkApiUri(uri.toString))
).map{
case (Some(lUri), Some(sUri)) => sUri
case (Some(lUri), None) => lUri
case (None, Some(sUri)) => sUri
case (None, None) => defaultUri
}.foldp(defaultUri){(curUriObj, newUriObj) =>
if (curUriObj.toString == newUriObj.toString) curUriObj
else newUriObj
}.dropRepeats.map{ capiUri =>
// DEBUG
println(s"current API URI is $capiUri; hash = ${System.identityHashCode(capiUri)}")
capiUri
}
The problem with foldp
here is that curUriObj
doesn’t come from the current value of currentApiUri
itself, but rather from its input sources:
(checkApiUri(localApiUri)
|@| apiUriApp.app._2.flatMap(uri => checkApiUri(uri.toString))
)
In our case, since checkApiUri
creates and returns a new URI
object every time it is called, foldp
always updates. A relevant part of the web console looks like this for a particular route:
"current API URI is http://localhost:8081/ced2ar-rdb/api; hash = 25" Dynamic.scala:78:70
"current API URI is http://localhost:8081/ced2ar-rdb/api; hash = 26" Dynamic.scala:78:70
"current API URI is http://localhost:8081/ced2ar-rdb/api; hash = 27" Dynamic.scala:78:70
"current API URI is http://localhost:8081/ced2ar-rdb/api; hash = 28" Dynamic.scala:78:70
An alternative is to define a version of dropRepeats
that maintains an internal Var
so that Var.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):
case class Comp[A](oldA: A, newA: A)
def dropRepeat[A](init: A, rx: Rx[A], keep: Comp[A] => Boolean): Rx[A] = {
object holder {
val hvar = Var(init)
val cc: Cancelable = rx.impure.foreach(newVal => hvar.update(curVal =>
if (keep(Comp(oldA = curVal, newA = newVal))) curVal else newVal
))
override def finalize(): Unit = {
try cc.cancel
finally super.finalize()
}
}
holder.hvar.dropRepeats
}
def keepUri(uris: Comp[URI]): Boolean = uris.oldA == uris.newA
val currentApiSource: Rx[URI] = (checkApiUri(localApiUri)
|@| apiUriApp.app._2.flatMap(uri => checkApiUri(uri.toString))
).map{
case (Some(lUri), Some(sUri)) => sUri
case (Some(lUri), None) => lUri
case (None, Some(sUri)) => sUri
case (None, None) => defaultUri
}
val currentApiUri: Rx[URI] = dropRepeat(defaultUri, currentApiSource, keepUri)
.map{ capiUri =>
// DEBUG
println(s"current API URI is $capiUri; hash = ${System.identityHashCode(capiUri)}")
capiUri
}
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 in case DropRep(self) =>
in rx.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:
scala> val uriA = new URI("https://www.ibm.com/us-en/")
uriA: java.net.URI = https://www.ibm.com/us-en/
scala> val uriB = new URI("https://www.ibm.com/us-en/")
uriB: java.net.URI = https://www.ibm.com/us-en/
scala> uriA == uriB
res0: Boolean = true
The equivalent web console output for our new dropRepeat
function is :
"current API URI is http://localhost:8081/ced2ar-rdb/api; hash = 19" Dynamic.scala:78:70
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.
Issue Analytics
- State:
- Created 6 years ago
- Comments:16 (11 by maintainers)
I think the point of good tests is to beat things to death 😃
@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 adropRepeats
that can inspect its current value using a specified “keep” function.It makes sense to me that you’d want to
dropRepeates
after aflatMap
, and not inside offlatMap
, but don’t see anything obvious popping up here (the onlyflatMap
in my code is the one you see) - I’ll see if I can make a minified example