Static Land interop `Observable` definition is wrong
See original GitHub issueLike a comment in the current definition says, the definition is wrong:
// This ap strictly speaking incompatible with chain. If we derive ap from chain we get
// different (not very useful) behavior. But spec requires that if method can be derived
// it must have the same behavior as hand-written method. We intentionally violate the spec
// in hope that it won't cause many troubles in practice. And in return we have more useful type.
ap(obsFn, obsVal) {
return combine([obsFn, obsVal], (fn, val) => fn(val))
},
I can see the “good intention” behind the above definition, but a major advantage of Static Land is precisely the fact that for a single type you can simply have as many different algebra module instances as needed:
- We can implement many modules for one type, therefore we can have more than one instance of the same Algebra for a single type. For example, we can implement two Monoids for numbers: Addition and Multiplication.
Observables support multiple different algebras. For example, basically every flatMapXXX
operation induces a Monad with different semantics. With Static Land, each of those can be supported as different algebra modules (all of them fully correct) from which the user can then pick and choose the appropriate one depending on which semantics are needed.
So, why is the above definition wrong? Because it breaks the sequential semantics of the chain. Let’s say that you wish to perform a sequence of, say, database operations using the Observable
definition. You should be able to do that with an applicative sequence
function, but now you cannot, because the above definition executes the operations in parallel, rather than sequentially.
Of course, another major advantage of Static Land is that this issue can be worked around simply by providing the proper algebra modules outside of the library core.
Issue Analytics
- State:
- Created 5 years ago
- Comments:5 (2 by maintainers)
Top GitHub Comments
@polytypic I’m not sure I agree that
ap
should have sequential semantics. Part of the purpose ofApply
is to have an option for parallel operations. I don’t know what would be the right authoritative source to cite so I will cite a few sources:https://typelevel.org/cats/typeclasses/parallel.html
https://hackernoon.com/functors-and-applicatives-b9af535b1440
That’s a quote from “Scala in Depth” by Josh Suereth, discussed here: https://stackoverflow.com/questions/12184373/how-do-applicative-functors-tie-in-with-parallelizing-algorithms-scala-and-sca
On the other hand I found this comment on Reddit that argues that an implementer should maybe consider making an
Apply
instance sequential for types that implementMonad
:https://www.reddit.com/r/haskell/comments/381o9y/does_applicative_have_an_inherent_notion_of_order/crrmnmr/
Replies to that comment point out some counterexamples.
There is also a discussion of “sequencing of effects” in Haskell on Wikibooks that sheds some light on the issue. The gist is that the result of
ap
in a case where inputs may represent multiple values (as is the case with Observable) is implementation-dependent.https://en.wikibooks.org/wiki/Haskell/Applicative_functors#Sequencing_of_effects
My interpretation of what I have read is that there is no absolute rule about the relationship between
ap
andchain
. I think that the implementation ofap
should be determined by use cases that are likely to be most useful, and results that are likely to be least surprising.Hm, right, in the case of one-value observables sequential
ap
might make sense indeed.Let’s discuss how exactly we could fix this. I think we shouldn’t change
Kefir.staticLand.Observable
, maybe just add a note in the documentation about its flaw. Instead we could add new modules, sayKefir.staticLand.ObservableMonad
andKefir.staticLand.ObservableApplicative
. Not sure about the names, and what exactly we should do about the methods: should we just omitap
inObservableMonad
, omitchain
inObservableApplicative
, and copy all the rest fromObservable
?