Comments (27)
ping @LukaJCB
from purescript-rxjs.
I disagree generally. Under your reasoning, memory allocation would be a side effect -- a true statement, but it's not really what we mean by side-effect.
Morally, this is a true statement:
Array a -> Observable a
The copying incident to moving from Array a
to Observable a
is an implementation detail.
And interval
is fine too, as the only way to usefully employ the interval
function is within an Eff
computation. Otherwise, interval is just a stream of integers.
from purescript-rxjs.
Under your reasoning, memory allocation would be a side effect a true statement, but it's not really what we mean by side-effect.
That was not my point.
From what I understand, a function without side effect can be cached
or inlined
according to the desires of the compiler.
val1 = let a = timer 3 in [a,a,a,a]
val2 = [timer 3, timer 3, timer 3, timer 3]
🔴 here, the compiler will arbitrarilly:
- sometimes transform
val1
intoval2
(pure function inlining) - sometimes transform
val2
intoval1
(pure function caching) - sometimes do nothing
val1 create one timer,
val2 create 4 timers, slightly different because of creation order.
-> I think this is not completely correct.
especially when tools like rollup-plugin-purs that exist
(rollup-plugin-purs already does inlining and assumes PureVars.)
This being said, I understand that
- right now, purescript doesn't inline or cache much
- semantics is close enough for things like interval or the probably the same for things like fromArray
from purescript-rxjs.
Would you maybe accept a PR with alternative effectful functions ? :)
from purescript-rxjs.
@rvion Good points. I wasn't thinking about (future(?)) inlining and caching having an impact on correctness -- the joys FFI.
I will definitely take a PR to fix, and not simply as an alternative. :) Thanks! (Obviously, FFI *.js
files will be impacted by the changes.)
I'm generally hesitant to add fancier types than needed, but it's unavoidable here.
from purescript-rxjs.
Hey guys, I agree in general about certain functions being effectful, but I'm actually not sure about fromArray
or interval
.
AFAIK interval
will not do anything at all until subscription, so
val1 = let a = interval 1000 in [a,a,a,a]
val2 = [ interval 1000, interval 1000 , interval 1000, interval 1000]
should be semantically the same. This is because the internal timer will not start until you actually call the subscribe
method and will also start a new internal timer for each subscription. Maybe I'm seeing things wrong here, but I don't think interval
would break from inlining or caching. :)
from purescript-rxjs.
@jasonzoladz thanks !
(future(?))
with rollup-plugin-purs, future is now :)
from purescript-rxjs.
here is another example with some pseudo effectful code
app1 = do
let a = interval 1 -- second
replicateM 5 do
sleep 1 -- second
push (div [childShow <=== a])
and
app2 = do
replicateM 5 do
sleep 1 -- second
push (div [childShow <=== interval 1])
those are different:
app1 will produce a list of 5 numbers incrementing every second
- 1
- 1
- 1
- 1
- 1
the other will produce a list of 5 successive numbers (all different) incrementing every second
- 1
- 2
- 3
- 4
- 5
but sadly, compiler may transform one into the other
from purescript-rxjs.
I experimented with timers today here: outwatch/purescript-outwatch@2a6f65b
and faced this concrete problem.
(edit ✏️ don't look too much at the Ok.purs module: it's only some test buffer I'm using ^^)
from purescript-rxjs.
This is because the internal timer will not start until you actually call the subscribe method and will also start a new internal timer for each subscription.
I would consider this as a bug IMO, because it is much less practical.
how could one bind the same timer to two different things if it was working like this ?
from purescript-rxjs.
I would consider this as a bug IMO, because it is much less practical.
how could one bind the same timer to two different things if it was working like this ?
This is definitely not intuitive and a matter of great debate. RxJS Observables are "cold" by default. To use the same timer on both you would have to use publish
and connect
or share
. There are a lot of articles out there about hot vs cold Observables. It's not immediately apparent, but I don't think its a bug IMO.
from purescript-rxjs.
@LukaJCB :O indeed, quite surprising
thanks for the explanation !
from purescript-rxjs.
I must be experimenting some other bug, so 🤔
I'll try to figure out what's happening on my side
from purescript-rxjs.
so, if some observables are cold by default,
functions like share or like publish (binding seems to be missisng) should be effectful
and unwrap shouldn't be (if, as I understand it, it doesn't apply the effect at this moment)
from purescript-rxjs.
@jasonzoladz @LukaJCB I'm reporting those as I'm trying to solve various bug related with observable scope / lifetime, but so far, not much luck, and I'm running out of ideas.
Animated gif:
here, I have some non trivial application with a widget-selector.
the initial widget loaded always work, but when I change to some other, it buttons don't work anymore and observable are mixed in a weird way that seems impossible according to the type system.
when I display the LifeCycle widget, it magically fixes the counter one and I can get back to it.
(I included a random number in the MonadicCounter widget so one can check that the the code widget code has ran again. I also have debug that show that new subjects are created. I'm really clueless about what's happening)
from purescript-rxjs.
in case you want to take a close look: code is here https://github.com/rvion/purescript-outwatch/tree/monadic-api
repo https://github.com/rvion/purescript-outwatch
branch monadic-api
I included the examples directly in the main src folder to ease the testing process.
copy pasting the following lines in a terminal should be enough to get the code, setup deps, and run a server serving the same application as in the above screenshot (with live reloading + hot module replacement)
git clone -b monadic-api https://github.com/rvion/purescript-outwatch.git
cd purescript-outwatch
npm install
pulp build
./node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot
then (facultative) in an other terminal (for fast recompilation):
pscid
from purescript-rxjs.
share
isn't effect-ful. (share
returns a new Observable.) See here. The hot
effect happens upon subscription. Once you're in Eff
you need to be aware of the semantics of hot and cold Observables.
Perhaps we could add something to the row of effects for subscription so as to highlight it for the user; though I usually find the effects tracking more trouble than it's worth. Once you're in Eff
, caveat emptor.
unwrap
is effect-ful because it performs the effect and returns the value wrapped in an Observable. The returned Observable can then be subscribed to later.
from purescript-rxjs.
@jasonzoladz I start to get a little bit lost x)
In your documentation, you say that share is an alias for publish().refCount()
but publish seems to be effectful because if you publish an interval observable that emit a number every seconds, it means you're starting a timer for real.
I understand that I don't know much about RxJS (maybe should I learn it first), so I may be wrong again here.
still, according to https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables, I really have the impression that transforming a cold observable into a hot one (via publish
and connect
or share
as @LukaJCB said) is effectful
is that wrong ?
from purescript-rxjs.
unwrap is effect-ful because it performs the effect and returns the value wrapped in an Observable. The returned Observable can then be subscribed to later.
again, I'm not so sure yet anymore, but maybe I'm just missing some key points.
RxJS doc says that an Observable is
A representation of any set of values over any amount of time. This the most basic building block of RxJS.
your unwrap function is just mapping a function that runs effects coming out of the observable.
exports.unwrap = function (obs) {
return function() {
return obs.map(function(eff) {
return eff();
});
};
}
but in my case, I'm unwraping a observable at time t=0, but at time t=1, some external event (mouse mouve) will generate a new value, I will map a function (MouseEvent -> Eff e Int) and use the int because the observable has been unwraped in the past.
It makes no sense to me right now as I just used the int
from an Eff e Int
created at t=1 just because I did unwrap
my observable.
I have the impression that
- unwrap existence implies that binding an observable must be effectuful (as effects are run for each new values
- but that storing (via a function) in our observable the information that we must perform some effects before outputing a value is not in itself effectful
from purescript-rxjs.
publish().refCount()
also doesn't perform an effect.
The docs re: refCount
say: "refCount makes the multicasted Observable automatically start executing when the first subscriber arrives, and stop executing when the last subscriber leaves." The key language is "when the first subscriber arrives."
In any event, an Observable does nothing until an Observer subscribes to it. (share
and publish
return new Observables -- creating a new specification for an effect-ful computation that might never happen).
I think what might be tripping you up is that it feels weird to have different behavior for subsequent subscriptions; I agree that it feels weird. But you could think of it like this: If you have two black-box Eff
computations and you called run
on both of them, you would expect that the different computations might do different things. subscribe
is like run
.
from purescript-rxjs.
@jasonzoladz thanks for your time and the explanations, I'll re-read that slowly tomorrow 👍
from purescript-rxjs.
exports.unwrap = function (obs) {
return function() {
return obs.map(function(eff) {
return eff();
});
};
}
Here's what's happening in the FFI... unwrap
returns a thunk (here, an Eff
computation). When that thunk is executed, it returns the Observable that, when subscribed to, will actually perform the eff()
.
from purescript-rxjs.
No problem. If I have time later, I'll take a look at your code to see if I can find the issue. Good luck!
from purescript-rxjs.
Here's what's happening in the FFI... unwrap returns a thunk (here, an Eff computation). When that thunk is executed, it returns the Observable that, when subscribed to, will actually perform the eff().
I understood that.
It just don't make much sense to me now why would the effect is attached to this obs.map rather than to the later subscirption where the effs will actually be run :)
No problem. If I have time later, I'll take a look at your code to see if I can find the issue. Good luck!
thanks ! :)
from purescript-rxjs.
Hey guys,
I also think share
should be effectful, atleast that's what my intuition tells me. Gonna have to think about it some more and experiment I guess.
About your bug @rvion that seems pretty puzzling to me, I'm gonna try and step through the code line by line later tonight to see what's going on.
from purescript-rxjs.
If I have time later, I'll take a look at your code to see if I can find the issue. Good luck!
About your bug @rvion that seems pretty puzzling to me, I'm gonna try and step through the code line by line later tonight to see what's going on
Thank you !
from purescript-rxjs.
Here's a quick script to show you what I mean. share
simply returns a new Observable that, when run/subscribed, will behave as a hot Observable. The call to share
does nothing to the original Observable; the shared Observable behaves as expected (i.e., new subscribers pick up elements mid-stream); and the original Observable can be used again.
The upshot is that until subscribe
is called an Observable doesn't do anything -- it's just a description of a computation to be performed. However, the semantics of that future computation are determined by the manner of creation.
<!doctype html>
<html>
<body>
<h1>Hello sailor!</h1>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.3.0/Rx.min.js"></script>
<script>
function main() {
var Observable = Rx.Observable;
var original = Observable.interval(1000);
var shared = original.share();
var originalSubscription1 = original.subscribe(function(n){
console.log("Logging Original No. 1 for 4 seconds: " + n);
});
setTimeout(function(){
originalSubscription1.unsubscribe();
console.log("Subscription to Original No. 1 cancelled.");
}, 4000);
var sharedSubscription1 = shared.subscribe( function(n){
console.log("Logging Shared No. 1 for 4 seconds: " + n);
}
);
setTimeout( function(){
sharedSubscription2 = shared.subscribe( function(n){
console.log("Delayed logging of Shared No. 2 for 2 seconds: " + n);
});
}, 3000);
setTimeout(function(){
sharedSubscription1.unsubscribe();
console.log("Subscription to Shared No. 1 cancelled.");
}, 4000);
setTimeout(function(){
sharedSubscription2.unsubscribe();
console.log("Subscription to Shared No. 2 cancelled.");
}, 5000);
var originalSubscription2 = original.subscribe(function(n) {
console.log("Second log of original for 2 seconds: " + n);
});
setTimeout(function(){
originalSubscription2.unsubscribe();
console.log("Original subscription2 cancelled.");
}, 2000);
}
window.onload = main;
</script>
</body>
</html>
And here's the output on the console:
from purescript-rxjs.
Related Issues (17)
- Documentation for using purescript-rxjs HOT 7
- Observable should be a Monoid for any a HOT 2
- Alt instance should behave the same as Semigroup instance HOT 4
- Add ObservableT Monad transformer HOT 6
- Publish latest (0.11 compatible) version to Bower HOT 2
- Allow a Subject to subscribe to an Observable HOT 5
- how to subscribe? HOT 1
- Quickcheck required? HOT 2
- `unsubscribe` causes runtime exception
- Support purescript-prelude 4.x HOT 1
- startWith doesn't work correctly HOT 2
- 'throttleTime is undefined' error when trying to use it HOT 2
- 'TypeError: tryCatchTarget.apply is not a function' when trying to use `windowWhen` HOT 2
- 'TypeError: obs.subscribe is not a function' when calling `empty` HOT 1
- 'TypeError: obs.subscribe is not a function' when calling `never` HOT 4
- Add operator defaultIfEmpty HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from purescript-rxjs.