question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Isolate alternatives with Subject and fractality issues

See original GitHub issue

This issue is basically a compilation of stuff said in the Gitter. Shoutout to @laszlokorte and @whitecolor for discussing these things with me. There are a lot of details, so feel free to point out and correct any mistakes.

Before you read any of this, I want to emphasize that idiomatic Cycle is currently neither truly pure nor truly fractal, but there are several possible solutions.

isolate is impure

isolate is necessary for ensuring two instances of a component are unique, but it also introduces a great amount of complexity to Cycle, and many drivers need to be aware of it. These circumstances create a lot of controversy over whether we should use isolate by default or if there is a better solution (e.g. #259).

Cycle’s goal is to be pure and referentially transparent, but as long as we have isolate, it cannot be.

isolate(Component) // impure
isolate(Component, scope) // pure <==> Component is pure

Since we can never be sure that a component is pure, it is safe to pessimistically assume that all components are impure.

The minimum requirements for a truly practical and pure isolate are threefold: (1) We must always pass an explicit scope to isolate (2) In order for this to scale, we must use a scope generator, from the top all the way down (humans are bound to create duplicates).

So far, the code would look like this:

(cc @theefer)

function Component(sources) {
    const iso1 = sources.isolate;
    const [scope1, iso2] = iso1.next();
    const [scope2, iso3] = iso2.next();

    const { isolate: iso4 } = Child({ ...sources, isolate: iso3 });

    // ...
    return {
        // ...
        isolate: iso4
    };
}

(3) This behavior is extremely redundant, and this purity criterion also requires us to pass it down as an extra sink on all components. The best way to practically do this is to use a pure functional language like Elm or Purescript, where we can then hide the isolate generator in a state monad.

The Fractal Problem

@staltz says that Cycle is a Fractal Architecture (http://staltz.com/unidirectional-user-interface-architectures.html). While this is true by interface, certain implementation details prevent it from being true in practice.

In Cycle’s case, being fractal means that you can take any app’s main function and pass it some drivers, or alternatively call a wrapped run(main, drivers) function, and expect it to behave normally.

But what if it uses isolate? We can’t be sure that the child (possibly third party) component uses the same instance that we use. In the current source, isolate always counts up from zero. This means that two components using different isolate instances can clash. We’ve decidedly refused to use random number generators, since they are never truly safe. Using an incremental id is usually the safest bet, but only in a singular context, which we cannot assume.

One possible solution would be to somehow make sure that all components receive the same isolate, but that would only increase Cycle’s overall complexity.

Subjects to the rescue?

This idea came with some initial disapproval, but I’ve thought very much about it and I think it is a possible way forward.

Instead of using DOM.select, we can use an event handler like in rx-recompose: https://github.com/acdlite/recompose/tree/master/src/packages/rx-recompose

(cc @vladap)

In the following code, DOM.handler is equivalent to createEventHandler in rx-recompose. createEventHandler simply returns a function that acts like Subject, but proxies calls to onNext. Using $x$ is just a contrived convention, but the notation indicates that it is both an observer and an observable (i.e. subject).

export default ({ DOM }) => {
-   Button(DOM.select('.Home')).forEach(() => {
-       window.location.href = '/';
-   });
-   Button(DOM.select('.Github')).forEach(() => {
-       window.location.href = 'https://github.com/edge/cyc';
-   });
+   const $goToHome$ = DOM.handler();
+   const $goToGithub$ = DOM.handler();
+   $goToHome$.forEach(() => {
+       window.location.href = '/';
+   });
+   $goToGithub$.forEach(() => {
+       window.location.href = 'https://github.com/edge/cyc';
+   });
    return {
        DOM: $.just(
            div('.p2.measure', [
                h2('About'),
                h4([
                    i('cyc'), ' is a Cycle.js boilerplate built with convenience and speed in mind.'
                ]),
                br(),
-               button('.btn.Home', 'Home'), ' ',
-               button('.btn.Github', 'Github'),
+               button('.btn', { onclick: $goToGithub$ }, 'Home'), ' ',
+               button('.btn', { onclick: $goToGithub$ }, 'Github'),
            ])
        )
    };
}

Although in the end we have two extra lines, this removes the need for isolate, drastically reducing complexity.

You might say that this is no longer pure, but isolate and Subjects are two sides of the same coin: http://i.imgur.com/gEJ2pxu.jpg

To the parent, the interface and behavior are the same. This follows two Cycle doctrines:

  1. All read effects are from sources. All write effects are to sinks. ++ Subjects are implicit sources and sinks.
  2. Parent sources go to Child sources, Child sinks go to Parent sinks. ++ As you can see in the diagram, no read-write arrow crosses a component context (lines in the diagram).

Not only is it more predictable, using subjects is about as pure as Javascript purity goes.

This also solves the fractal problem, because all Subjects are inherently unique.

What using this idiom implies for other drivers is an important topic for discussion, which I’ll start off with this dialogue: Q. How would this work for the HTTP driver? A. { url, response$: $response$ } Q. But doesn’t that violate the second doctrine? The subject will be passed into a driver, and a read event will bypass the parent sources. A. Possibly, but if you look at it like metadata, which is more obvious as we are only passing a function containing a reference to the actual subject, it is no different from theoretical driver interop. The handler is an implicit source/sink, but we’ve conflated the two for simplicity. Although it may not seem to follow the doctrine, it is still fractal.

But what about cycle-restart?

Using a subject for DOM events seems to exclude it from cycle-restart. In this case, we can add some code to the DOM driver to replay the subject’s last event. This also solves https://github.com/Widdershin/cycle-restart/issues/41.

(cc @Widdershin)

If this concept becomes idiomatic, it could be standardized and applied to other drivers. For example, an HTTP.handler() could be implemented, mitigating the aforementioned context-crossing.

The Fractal Problem (cont.)

This discussion does, however, bring up an important issue. Ideally, in an application implementing cycle-restart, every driver in an application should be restartable. This is the only way for an application to be truly restartable, as child applications with drivers would otherwise be ignored. In order for this to work, we need to explicitly pass down rerun to every component.

But what if a third party application uses its own instance of cycle-restart? This instance problem concerning cycle-restart, isolate, and other similar implementations could be partially solved by wrapping every application’s run(main, drivers) as follows:

export default ({ DOM, HTTP }) => {
    run(main, {
        DOM: DOM || restartable(makeDOMDriver('#root')),
        HTTP: HTTP || restartable(makeHTTPDriver())
    });
}

or possibly:

export default availableDrivers => {
    run(main, {
        DOM: restartable(makeDOMDriver('#root')),
        HTTP: restartable(makeHTTPDriver()),
        ...availableDrivers
    });
}

This means any available drivers would be reused, and would automatically be restartable. However, this would still require us to pass down restart and isolate in every component.

And what if we wanted hydratable drivers (not yet implemented)? Now every driver would also need to be wrapped in hydratable. This cannot scale.

Cycle seems to require a lot of passing-down-common-objects in order to be more correct as an architecture. While React solves this with this.context available to all impure components, we must solve it in a pure way to get closer to our goal of purity.

Monads are a viable solution; they are great for concisely and efficiently keeping things in a context, as well as reading and writing any necessary information. This implies that we should switch to a language that can facilitate this, like Elm or Purescript.

Conclusion

  1. Using Subjects instead of isolate would reduce a lot of complexity and make components much more predictable. It should live comfortably in both Javascript and compile-to-js environments.
  2. More importantly, the need for the switch to a functional language like Elm or Purescript to solve certain fundamental problems may be more imminent than we thought.

Gists:

  1. https://gist.github.com/edge/5a5be8fed277bce2389e
  2. https://gist.github.com/edge/39c1223f8f0e27a14e94

Further Reading:

  1. https://gitter.im/cyclejs/core?at=56d7436c0bdb886502f6d263
  2. https://gitter.im/cyclejs/core?at=56e5cd900055f8f35a82ccce
  3. https://gitter.im/cyclejs/core?at=56e6002e89dd3cce10061e42
  4. https://gitter.im/cyclejs/core?at=56e62ff13194fbd11096bb33

related: #259

Issue Analytics

  • State:open
  • Created 8 years ago
  • Reactions:6
  • Comments:16 (11 by maintainers)

github_iconTop GitHub Comments

4reactions
Widdershincommented, Mar 14, 2016

I think that there’s value in discussing these ideas, but they overlook a key aspect of what I think is one of Cycle’s biggest strengths, and the biggest reason to use it over Redux/Flux/Elm.

In Cycle, applications read from top to bottom in the following order:

  • Given that changes to state are driven by these actions
  • Change state
  • Display state to the user

They are extremely easy to read once you’re familiar, as you can always trace the source of where some data is flowing from just by reading the definitions.

With the ideas proposed in this issue, the order would be more like Redux/Flux/Elm.

  • Given that some actions will happen, change state
  • Display state to the user
  • Handle user actions, trigger the above state changes

The difference there is that any part of your view can dispatch an action and change the application. You can’t just trace back the flow of definitions to find all changes to state, you have to read the whole app.

In my opinion, Subject should not be used in application code. It makes it much harder to trace the flow of data in your app. They aren’t declarative, and as a result usage isn’t self describing.

Purity is a tradeoff. I think it’s much more compelling to aim for the most useful feature of pure functions, writing deterministic applications.

cycle-restart demonstrates that a deterministic Cycle app where all changes to state are driven by external drivers allows you to treat the app as if it were a pure function.

The architecture problem I’m most excited for right now is how we declaratively express the relationships between nested dataflow objects, without using Subject. @staltz has said before that there’s top/down and bottom up, but no middle. I’m not quite sure. I think there’s room to investigate relational ideas and solve problems that way.

2reactions
wclrcommented, Mar 14, 2016

I think using explicit isolate and really cyclic approach makes developer to think more - and in my view that is a good thing and using of Subject is seem to be a hack to me because there this no clear repsonsibility separation between READ/WRITE effects - and this is a conner stone in good design and architecture.

People complain that cycle is forcing to do some boilerplate and “care about differnt things more”. But if you don’t want to explicit, concise, logically verbose, you probably don’t want to use cycle and declartive aproach in general. There are a lot of framewords that “hide” rough spots and do a lot of “magical” stuff (that actually often turns in to “magical” bugs).

There is a great article by @staltz http://staltz.com/its-easy-to-write-imperative.html. I think the more time and effort invested in thinking about apps logic, possible flaws and conner cases the less time in perspective will be required to support and modify this logic - this what functional programming and cycle in paticular it just forces us to think more. From practice: even the people who don’t know the cycle and actually are yet very bad at understanding functional approach and struggling with it feel by their gut that program becomes more reliable when they start to use this approach.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Fractal: an educational model for the convergence of formal ...
Advisers or counselors may exist to resolve doubts. Learning occurs in isolation. Used in formal and non-formal education programs. It is more ...
Read more >
Pitfalls in fractal time series analysis: fMRI BOLD as ... - Frontiers
Recently, a rapidly increasing volume of experimental data has demonstrated that BOLD is a complex signal, whose fractality – if properly ...
Read more >
Social isolation and the brain in the pandemic era - Nature
Intense sociality has been a catalyst for human culture and civilization, and our social relationships at a personal level play a pivotal ...
Read more >
UNDERSTANDING FRACTAL ANALYSIS? THE CASE OF ...
knowledge that enables others to gain control of the discourse (subject ... The problems which surround fractal research can be illustrated by the...
Read more >
Fractal-Scaling Properties as Aesthetic Primitives in Vision ...
The fractal-like scaling characteristics are ubiquitous in many physical ... (2016) presented these synthetic images in a two-alternative ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found