Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove imitate #305

Open
staltz opened this issue Jun 30, 2020 · 8 comments
Open

Remove imitate #305

staltz opened this issue Jun 30, 2020 · 8 comments

Comments

@staltz
Copy link
Owner

staltz commented Jun 30, 2020

imitate was deemed necessary long ago before we had @cycle/state and other facilities. Internally, it requires significant "machinery" to work. For instance, see private methods _hasNoSinks (recursive!) and _pruneCycles.

Nowadays, I don't see use of imitate, neither in the wild, neither in my apps. I would like to remove imitate, to decrease the size of xstream and make it faster too.

In this issue I'd like to hear from people if they use imitate or not. Of course if there's still a real use case for it (not affected by the new facilities in Cycle.js), I'd like to hear it instead of just going ahead with this plan.

@wclr
Copy link
Contributor

wclr commented Jul 22, 2020

I use imitate in one place =) probably need to replace it.

@staltz
Copy link
Owner Author

staltz commented Jul 22, 2020

Is it something you'd like to keep? And could you show how your usage looks like?

@wclr
Copy link
Contributor

wclr commented Jul 22, 2020

I use to circulate innter componets's state stream from bottom to top of the function just like it is intended to work. Need to use something like statify wrapper or something like that (in other couple of places I use another solution for this, bu not statify though, but it uses the same principle I believe).

Circulating inner state of the component is not so often task in my experience, but sometimes it is needed.

@fuunnx
Copy link

fuunnx commented Jul 24, 2020

I can confirm I use it in (only) one place too, the usage looks like this :

const generatePdfIntent$ = xs.create()
const pdf = PdfBuilder({ ...sources, generatePdfIntent$ })
const form = Form({ ...sources, generatedPdf$: pdf.generatedPdf$ })
form.submit$.imitate(generatePdfIntent$)

However I believe this code could be refactored because Form actually handles too much concerns

Hope it helps

@CarlLeth
Copy link

CarlLeth commented Jul 31, 2020

I use xstream without cycles.js, and I use imitate often. I commonly have pairs of streams that look like (data: Stream<T>, updates: Stream<T => T>). Data gets broken down from high-level collections of objects into low-level fields, those low-level fields get mapped to input elements, then the input elements provide update streams that get built back up via merge. There's a cycle: updates modify data, and data determines what updates are possible.

This is a simple use case; at least as simple as I can get it while still demonstrating everything. It relies on a streamCollection, a mutable collection that merges streams as they are added using imitate. stateMachine is just a fancy fold that applies the updates to the incoming data stream. m is mithril.js.

I've also included a diagram of what's going on, which may or may not illustrate the point any better than the code. Blue dashed boxes are streams.

If you have a better solution that doesn't require imitate (or cycle.js), I'm all ears.

function createBookTable(sourceData: Stream<Array<Book>>) {

    // Mutable collection of merged streams that relies on imitate
    const updates = new StreamCollection<(books: Array<Book>) => Array<Book>>()
    const liveData = stateMachine(sourceData, updates.stream);

    const viewModels = liveData.map(books => books.map(createBookVM));

    const tableVnodes = viewModels
        .map(arr => xs
            .combine(...arr.map(vm => vm.vnodes))
            .map(rows => m('table', rows))
        )
        .flatten();

    const arrayUpdates = viewModels
        .map(arr => xs.merge(...arr.map((vm, i) => vm.updates.map(update => (data: Array<Book>) => {
            const clone = [...data];
            clone[i] = update(data[i]);
            return clone;
        }))))
        .flatten();

    updates.add(arrayUpdates); // Stream.imitate happens here

    return {
        vnodes: tableVnodes,
        updates: updates.stream
    };
}

function createBookVM(book: Book) {
    const inputs = {
        author: new TextInput(book.author),
        title: new TextInput(book.title)
    };

    return {
        updates: xs.merge(
            inputs.author.updates.map(text => (book: Book) => <Book>Object.assign({}, book, { author: text })),
            inputs.title.updates.map(text => (book: Book) => <Book>Object.assign({}, book, { title: text }))
        ),
        vnodes: xs.combine(
            inputs.author.vnodes.map(input => m('td', input)),
            inputs.title.vnodes.map(input => m('td', input))
        ).map(cells => m('tr', cells))
    };
}

interface Book {
    author: string,
    title: string
}

class TextInput {
    updates: Stream<string>;
    vnodes: Stream<m.Vnode>;
    //...
}

reactive edit loop

@bgfist
Copy link

bgfist commented Sep 16, 2020

I think imitate could be replaced by defer like in rxjs, here's my silly implementation for defer.

function defer<T>(create: () => Stream<T>) {
  let sub: Subscription;

  return xs.create<T>({
    start(observer) {
      sub = create().subscribe(observer);
    },
    stop() {
      sub?.unsubscribe();
    },
  });
}

and to use it like below:

const someOther$ = ...;

const a$ = someOther$.compose(sampleCombine(defer(()=> b$).startWith(...);
const b$ = a$.filter(...).map(...).startWith(..);

cause b$ is accessed in a closure, there's no syntactics error.
and a$/b$ must have an initial value(MemoryStream), otherwise both will be halting.
this may cause infinite loop, but that depends on your logic.

and it's really necessary for memory streams to depend on each other, cause in most cases both will filter out(in any way, like filter、sampleCombine、endWhen) those changes cause by themselves to avoid infinite loop.

@staltz
Copy link
Owner Author

staltz commented Sep 16, 2020

@bgfist Unfortunately imitate is vastly more complex than defer, see issue #51.

@brookback
Copy link

We use imitate without CycleJS in our app. It's similar to CycleJS in its cyclical nature though.

Example structure:

const browserOut$ = xs.create<driver.BrowserOut>();
const browserIn$ = driver.BrowserDriver(browserOut$);

const sinks = app(sources);

browserOut$.imitate(sinks.browserOut$);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants