Giter Site home page Giter Site logo

Return value is not chained? about turbine HOT 16 CLOSED

funkia avatar funkia commented on May 18, 2024
Return value is not chained?

from turbine.

Comments (16)

limemloh avatar limemloh commented on May 18, 2024 1
const safeDiv = (n, m) => m === 0 ? nothing : just(n / m);
go(function*() {
  const a = yield find(isEven, list1);
  const b = yield find(isEven, list2);
  const c = yield safeDiv(a, b)
  return a + b + c;
});

...
My guess is that the list is like array monad here
and the end result is wrapped into the list as in Haskell.

No, It is not the list/array monad. According to Jabz documentation, find returns a Maybe<A> which is either a just a or nothing. This makes sense, since searching for something in a list, will only maybe give you a result otherwise nothing.

Chaining a Maybe is like saying: "Calculate this unless you get a nothing, in that case, the whole result is nothing"
since go-notation is just sugar for calling chain, the example returns nothing if any of the yielded functions returns nothing otherwise it returns just d where d is the result of the computation.

Transferring to the Component monad case here, that should mean
the return value is wrapped into the Component.

yes, this is correct.

But what does it mean for the returned empty object {} in this example:
...

it means that if main were made to a Component using go-notation, running/yielding this component would return {}.

from turbine.

limemloh avatar limemloh commented on May 18, 2024

You need to yield the component in order to make it work

function* main() {
  yield label('Please enter your name:');
  yield input();
  return {};
}

Here is why:

modelView second argument is a function returning a Component.
So this should work:

function main() {
  return input()
}

If you want to chain a lot of components together you use the handy go function, which in this case returns a component.

function main() {
  return go(function*() {
    yield label('Please enter your name:')
    yield input();
    return {};
  });
}

because we ended up using go a lot inside the view, we decided to overload the modelView function to take a generator function and in that case, it would wrap the generator function in a go.
So we can instead write:

function* main() {
  yield label('Please enter your name:')
  yield input();
  return {};
}

The reason why your top example doesn't work is because in go you yield components and return the final output of the resulting component, which means that you are returning a component with a component as output.

The reason why your bottom example works must be due to a bug. It seems that modelView treats it like a normal function, instead of a generator function as it should have done.

from turbine.

dmitriz avatar dmitriz commented on May 18, 2024

So this producers errors:

import { runComponent, elements } from "../../src"
const { label, span, input, div } = elements

function main() {
  return input()
}

runComponent("#mount", main);
ERROR in ./hello-world/index.ts
(11,24): error TS2345: Argument of type '() => Component<InitialOutput<{ actionDefinitions: { focus: (element: HTMLElement) => void; }; be...' is not assignable to parameter of type 'Child<{}>'.
  Type '() => Component<InitialOutput<{ actionDefinitions: { focus: (element: HTMLElement) => void; }; be...' is not assignable to type '() => Iterator<any>'.
    Type 'Component<InitialOutput<{ actionDefinitions: { focus: (element: HTMLElement) => void; }; behavior...' is not assignable to type 'Iterator<any>'.
      Property 'next' is missing in type 'Component<InitialOutput<{ actionDefinitions: { focus: (element: HTMLElement) => void; }; behavior...'.

The same for the other plain function.

There seems to be some problem when passing functions directly to the runComponent.

The modelView is not used in these examples, is it used implicitly?

from turbine.

limemloh avatar limemloh commented on May 18, 2024

That is because runComponent takes a Component and not a function.
so to fix:

runComponent("#mount", main());

from turbine.

dmitriz avatar dmitriz commented on May 18, 2024

That is because runComponent takes a Component and not a function.

Ah this feels confusing.
It does accept generators that are also not components, but not functions that are similar.

Would there be any problem to recognise a function and call it?
Then you would be able to switch generators with functions with only one * as difference :)

from turbine.

paldepind avatar paldepind commented on May 18, 2024

I can see how that is confusing. Maybe we should remove the overload so that always requires a Component. Then one has to use go like this:

const main = go(function* () {
  yield label('Please enter your name:')
  yield input();
  return {};
});
runCompoent(main);

Maybe that is less confusing as it makes it more obvious that runComponent only handles a component and that go is the function used to turn a generator function into a component.

from turbine.

dmitriz avatar dmitriz commented on May 18, 2024

The reason why your top example doesn't work is because in go you yield components and return the final output of the resulting component, which means that you are returning a component with a component as output.

Thank you, I was confusing the yield and return values,
where the difference is only in the done prop.
But apart from the done, both yield and return values work exactly the same way.
And the difference in the done only refers to whether the execution is finished,
not to the nature of the yields.

So it feels somewhat confusing to treat them differently the yields from the return value.

I was assuming some more uniform behavior, where it would not matter.
All components would get combined and all other yields or returns would be the output.
Whether it is generator or function.

That way the user would not have to think, which is always nice :)

Would you see any problem with that approach?

from turbine.

paldepind avatar paldepind commented on May 18, 2024

Would you see any problem with that approach?

Yes. Then users would have to return a monad in their generator function. And do-notation most often ends with a pure value and not a monad (that's why return is called return in Haskell).

If we changed it, a model couldn't look like this:

function* model({ ... }) {
  ...
  return { .. };
}

It would have to look like this:

function* model({ ... }) {
  ...
  return Now.of({ .. });
}

And a view couldn't look like this:

function* view({ ... }) {
  ...
  return { ... output ... };
}

It would have to look like this.

function* view({ ... }) {
  ...
  return Component.of({ ... output ... });
}

It would be a lot more cumbersome to use I think.

from turbine.

dmitriz avatar dmitriz commented on May 18, 2024

@paldepind

I can see how that is confusing. Maybe we should remove the overload so that always requires a Component.

I would only keep the go if you intend to use generators as they are, without the go.

Say if you really want to model a dialogue, where components appear in real time as the user types and hits return. That is actually a very important functionality that is painful to get with most frameworks, so having that type of use for generators would be neat!

Say I declare my component as

const main = function* () {
    yield div("Welcome to our dating advice")
    yield lable("Please type your name")
    const {value: name} = yield input()
    yield div(["Hello ", name, "how old are you?"])
    const {value: age} = yield input()
    ...
}

That would evolve as the questions proceed with more elements to appear in real time.
And using generators here is very intuitive because that is exactly how they normally work.
And the combination of intuitiveness, ease of use and usefulness can do great and make the framework really stand out.

That would be another "role" for the generators as I previously suggested.

Would it make sense?

from turbine.

dmitriz avatar dmitriz commented on May 18, 2024

Yes. Then users would have to return a monad in their generator function. And do-notation most often ends with a pure value and not a monad (that's why return is called return in Haskell).

I see.
But can we not somehow overload the monads?

If the components are distinguishable from other values (e.g. via Symbol props),
they can be collected and chained separately.

All other values can be collected in array in the order of their yields.
That may also work nicely for small views, where counting the order is not a problem,
similar to small functions, like in ramda, where you rather use unnamed parameters,
making them usually more reusable and generic.

Right now, I am forced to give names to my outputs, which is kind of hurting their reusability.

I would say, on the conceptual level, since the yields appear sequentially,
it feels natural to collect them in array in the same order.

And if you like to merge them into object, you can easily do it by passing objects
and folding their array with the merge reducer.
That would also solve nicely the key overlapping problem :)

Would it be a crazy idea?

from turbine.

dmitriz avatar dmitriz commented on May 18, 2024

I have compared with how the go works in Jabz
but the relation with the abstraction there seems confusing to me.

The Jabz go description only gives this example:

const safeDiv = (n, m) => m === 0 ? nothing : just(n / m);
go(function*() {
  const a = yield find(isEven, list1);
  const b = yield find(isEven, list2);
  const c = yield safeDiv(a, b)
  return a + b + c;
});

but does not seem to say anything about the lists, nor about the result of this computation.
My guess is that the list is like array monad here
and the end result is wrapped into the list as in Haskell.

Transferring to the Component monad case here, that should mean
the return value is wrapped into the Component.
But what does it mean for the returned empty object {} in this example:

function* main() {
  yield label('Please enter your name:');
  yield input();
  return {};
}

It does not seem to be wrapped in the monad here, does it?

from turbine.

dmitriz avatar dmitriz commented on May 18, 2024

@limemloh
Thanks for the explanation, yes, I've overlooked that find changed the type from List to Maybe, makes perfect sense.

I can see that switching to the de-sugared chain version
helps to restore the world :)

So just to check:

go(function*() {
  const a = yield just(2)
  return 1
});

desugars to

just(2).chain(() => 1)
// => just(1)
go(function* {
  yield div('Hello')
  return 1
})

desugars to

div('Hello').chain(() => 1)
// => div('Hello') decorated with 'output' = 1
go(function* {
  yield div('Hello')
})

is the same as

go(function* {
  yield div('Hello')
  return undefined
})

which desugars to

div('Hello').chain(() => undefined)
// => div('Hello') decorated with 'output' = undefined
  1. my first example above
function* main() {
  yield label('Please enter your name:')
  return input()
}

when wrapped with go, desugars to

label('Please enter your name:').chain(() => input())
// label(...)  decorated with 'output' = input()

So the component is sent to the output as you point out.

  1. my second example above
function* main() {
  return input()
}

when wrapped in go, desugars to simply

input()

because there is nothing to chain,
which explains the current behavior,
so maybe no bug after all?

  1. and this confusing one
function* main() {
  return yield input()
}

is a shortcut for the more digestible

function* main() {
  const a = yield input()
  return a
}

which desugars to

input().chain(a => a)
// => input() keeping its complete output

whereas

function* main() {
  yield input()
}

desugars to

input().chain(() => undefined)
//=> input() with output undefined

BTW, none of the tests for the go
seems to treat the boundary case with with the return missing.

Please correct me if there is still any flaw in my reasoning :)

from turbine.

paldepind avatar paldepind commented on May 18, 2024

@dmitriz

You are almost corret 😄

One must return a monad in chain. So this doesn't work:

just(2).chain(() => 1)

The last chain should be a map.

so maybe no bug after all?

It was a bug. It should be fixed in the latest version of Jabz.

BTW, none of the tests for the go seems to treat the boundary case with with the return missing.

I think that is right. But, not returning is equivalent to returning undefined so I think it's ok.

from turbine.

dmitriz avatar dmitriz commented on May 18, 2024

@paldepind

Oh, so you are using JS return like Haskell return (as you said above that I regarded as pun :)

Both clever and surprising. ;)
As they call the Haskell's return as of in JS, I didn't regard the two returns as related.

A normal JavaScripter using generators with yield and return with almost the same meaning might be surprised :)

That component is a function of its parent, adds a bit of complexity too.
Is it just to facilitate concatenation into DOM fragments,
whereas all needed from the parent is the attach method?

from turbine.

limemloh avatar limemloh commented on May 18, 2024

That component is a function of its parent, adds a bit of complexity too.
Is it just to facilitate concatenation into DOM fragments,
whereas all needed from the parent is the attach method?

I like that idea. The only problem I see is that a component might do more than just concatenate DOM fragments. dynamic, a function that takes a Behavior<Component<A>> and returns a Component<Behavior<A>>. The returned component behaves like the current component of the behavior. This requires dynamic to be able to remove the DOM of the previous component.
But an attach method and a detach method should be enough. 😃

from turbine.

dmitriz avatar dmitriz commented on May 18, 2024

@limemloh
Interesting, is it similar to Ramda's sequence?
I have been wondering what the dynamic is doing exactly.
Is there any description of it to read?

from turbine.

Related Issues (20)

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.