Code Monkey home page Code Monkey logo

Comments (27)

rvion avatar rvion commented on August 29, 2024

ping @LukaJCB

from purescript-rxjs.

jasonzoladz avatar jasonzoladz commented on August 29, 2024

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.

rvion avatar rvion commented on August 29, 2024

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 into val2 (pure function inlining)
  • sometimes transform val2 into val1 (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.

rvion avatar rvion commented on August 29, 2024

Would you maybe accept a PR with alternative effectful functions ? :)

from purescript-rxjs.

jasonzoladz avatar jasonzoladz commented on August 29, 2024

@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.

LukaJCB avatar LukaJCB commented on August 29, 2024

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.

rvion avatar rvion commented on August 29, 2024

@jasonzoladz thanks !

(future(?))

with rollup-plugin-purs, future is now :)

from purescript-rxjs.

rvion avatar rvion commented on August 29, 2024

@LukaJCB

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.

rvion avatar rvion commented on August 29, 2024

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.

rvion avatar rvion commented on August 29, 2024

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.

LukaJCB avatar LukaJCB commented on August 29, 2024

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.

rvion avatar rvion commented on August 29, 2024

@LukaJCB :O indeed, quite surprising
thanks for the explanation !

from purescript-rxjs.

rvion avatar rvion commented on August 29, 2024

I must be experimenting some other bug, so 🤔
I'll try to figure out what's happening on my side

from purescript-rxjs.

rvion avatar rvion commented on August 29, 2024

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.

rvion avatar rvion commented on August 29, 2024

@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:

buggy

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.

rvion avatar rvion commented on August 29, 2024

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.

jasonzoladz avatar jasonzoladz commented on August 29, 2024

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.

rvion avatar rvion commented on August 29, 2024

@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 shareas @LukaJCB said) is effectful

is that wrong ?

from purescript-rxjs.

rvion avatar rvion commented on August 29, 2024

@jasonzoladz

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 Intcreated 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.

jasonzoladz avatar jasonzoladz commented on August 29, 2024

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.

rvion avatar rvion commented on August 29, 2024

@jasonzoladz thanks for your time and the explanations, I'll re-read that slowly tomorrow 👍

from purescript-rxjs.

jasonzoladz avatar jasonzoladz commented on August 29, 2024
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.

jasonzoladz avatar jasonzoladz commented on August 29, 2024

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.

rvion avatar rvion commented on August 29, 2024

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.

LukaJCB avatar LukaJCB commented on August 29, 2024

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.

rvion avatar rvion commented on August 29, 2024

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.

jasonzoladz avatar jasonzoladz commented on August 29, 2024

@rvion @LukaJCB

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:

screen shot 2017-04-14 at 10 16 03 am

from purescript-rxjs.

Related Issues (17)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.