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

Reactive Magic #51

Open
paldepind opened this issue May 23, 2017 · 18 comments
Open

Reactive Magic #51

paldepind opened this issue May 23, 2017 · 18 comments

Comments

@paldepind
Copy link
Member

@ccorcos

This is an answer to paldepind/flyd#142 (comment).

Again, thank you for the feedback and thank you for taking the time to take a look at Turbine. It is really useful to get feedback like that and I'm grateful that you're shaing you opinion 😄

Spent some more time looking through Turbine this morning... That's some pretty intense stuff! It's very well thought-out, but building a mental model for how it all works is pretty challenging. I think if you included explicit type annotations in the tutorial, it would be a lot easier to pick up on. I think it might also help me understand how everything works if you had an example that showed me how to get all the way down to the actual DOM node where I could do things like call scrollTo() or instantiate a jQuery plugin or something.

For using a jQuery plugin we'd probably have to create a mount hook that gives the raw element. We haven't done that yet though. Regarding scrollTo I'll get back to you with an example.

I see what you mean about the differences though. It actually is a bit different. No selectors is 👍 and the code is really clean. I'm still trying to figure out where the challenges will be...

Let me know if you figure it out 😄. We have tried to make Turbine as powerful as possible. There are some functional frameworks that achieve purity by limiting what one can do. In Turbine we have tried to create an approach that is pure but without making things harder.

If you had a global application state for sidebarOpen that you needed to access in many places, I'm assuming this would just be a behavior that you can just import and combine in a model function? It wouldn't be pure though, right?

Turbine is completely pure. The answer to the question "is it pure" should always be "yes". sidebarOpen would probably be created inside a component and then the component would have to pass it down the children that need it.

You wouldn't be able to just import it. We can do that with a few behaviors. For instance, the mouse position, the current time and keyboard events can simply be imported. That is because they "always exist" in the browser. But sidebarOpen would be created inside some component so it can't be a global behavior that can be imported.

To stretch this abstraction even further, I might want to have two counters: the first counter has a delta of 1, and the second counter has a delta of the value of the first counter. Here's how I would do it using reactive-magic:

That is a good example. Here is how one could write that using Turbine.

const counterModel = go(function* ({ incrementClick, decrementClick }, { delta }) {
  const increment = snapshot(delta, incrementClick);
  const decrement = snapshot(delta.map(n => -n), decrementClick);
  const changes = combine(increment, decrement);
  const count = yield sample(scan((n, m) => n + m, 0, changes));
  return { count };
});

const counterView = ({ count }) => div([
  button({ output: { decrementClick: "click" } }, "dec"),
  span(count),
  button({ output: { incrementClick: "click" } }, "inc")
]);

const counter = modelView(counterModel, counterView);

const app = go(function* () {
  const { count } = yield counter({ delta: Behavior.of(1) });
  yield counter({ delta: count });
});

You can check out the example live here.

What intrigues me so much about this example is how clean the mental model is. It feels very easy to make sense of to me.

I think your example is nice. But, I think the one I wrote in Turbine is even better. I think the Turbine code avoids some problems. Problems that I see in many frameworks and some we've particularly tried to avoid in Turbine. Here are some of the problems.

  1. The definitions lie. For instance, the line delta = new Value(1) doesn't actually tells me what delta is. It says that delta is equal to 1 but that obviously isn't the entire truth. This means that if I want to know what delta actually is I'll have to find all the lines that use delta. In a real app that can be hard.
  2. When I look at the inc method I cannot see who may call it. It might be called once, twice, or many times in the view. This makes it hard to figure out when exactly the side-effect that inc has is triggered.
  3. The input to counter is mixed with the output from the counter. For instance, this line:
<Counter count={this.delta} delta={1}/>

It looks like both count and delta are input to the component. But, since Counter calls
update on count it actually uses it as an output channel. This means that whenever a Value instance is passed to a component it's hard to know if it's actually input or output.

I apologize for being a bit hard on your example 😅 The problems I pointed out are found in most React code. Let me explain how Turbine avoids them.

  1. In Turbine definitions always tells the entire truth. Every time a line begins with const the definition tells everything about the defined value. This makes it very easy to look at the code and figure out what things are.
  2. There are not impure methods in Turbine. You never looks at something and wonder "who are actually triggering this code?". The code always clearly describes where things are coming from.
  3. In Turbine a component function takes all its input as arguments and all its output is part of its return value. This makes a lot of sense. Input is arguments to functions and output is returned from functions. For instance, in the Turbine example, counter is a function that returns Component<{count: Behavior<number>>}.

I think the properties I described above makes Turbine code easy to understand and will make it scale really well with increasing complexity.

@ccorcos
Copy link

ccorcos commented May 23, 2017

I hear you on all those points. I'm a huge fan of purity and functional programming, but I've had a slight change of perspective recently. I was diving deeper and deeper into point-free programming because it was so declarative, but I started to realize how opaque it was.

I then realized that my real interest is the "mental models" we create to represent the things we think about, and not necessarily the mathematical beauty of functional programming. To give you a little taste, I wrote this article about a better mental model for number systems, and I created this prototype instrument to explore a better mental model for creating music (select the notes in your scale on the left and play them on the right or using the asdf keys on your keyboard, rotate the pie to change modes, slide the keyboard to change inversions).

So now back to Reactive Magic and Turbine -- I totally agree with all of your points, but what if we think about these things from a mental model perspective. A Value is like a cell in Excel. Its totally impure, but the abstraction clean and intuitive. When you say "The input to counter is mixed with the output from the counter.", my thinking is "That's exactly whats happening!". We're constructing a machine using Values as an abstraction over things that change and wire all these pieces together as if they aren't changing. What make Reactive Magic so interesting to me is the mental model fits very comfortably in my head. I don't have to think about thing. I can just grab the Values I need, put them together, and it works!

I have some more ideas when it comes to programming mental models, but I'm really interested to understand Turbine better. It amazes me that the program you wrote works the way you intend. I don't think I can fully understand this without seeing the type definitions written out. I can't even figure out why we are yielding anything from the counterModel.

As far as the jQuery plugin concept, one thing I like about arbol is how I've created a clean division between the pure and declarative world, and the impure effectful world. Integrating an external library is actually quite easy. For example, I'm using keymaster for reacting to hotkey events. I create an element that is effectively just a mock HTMLElement, and then I write some hooks to patch elements together. Maybe this approach is totally irrelevant for Turbine, but I thought it was worth noting because it gives you a way of escaping all the gnarly abstraction to write your own effectful imperative code...

@paldepind
Copy link
Member Author

@ccorcos

I was diving deeper and deeper into point-free programming because it was so declarative, but I started to realize how opaque it was.

I definitely agree that point-free programming can be very hard to read. But I don't agree that point-free programming is more declarative. I'm not really a fan of the way in which many people use the word "declarative" to mean "doesn't look like actual code". IMO as long as a function is pure it is 100% declarative. It doesn't get more declarative just from being written in a point-free style.

I then realized that my real interest is the "mental models" we create to represent the things we think about, and not necessarily the mathematical beauty of functional programming.

I completely agree that mental models are highly important. But, I would argue that mental models go hand in hand with math. Because math gives us a precise language. And without precision our mental models are just handwaving. I'm very inspired by Conal Elliott who invented FRP. He talks a lot about having simple mental models (he just calls it "semantic", i.e. figuring out the meaning of things). And one of his major points is that a mental model can't be simple without being precise. Because if we can't be precise about it then we don't really understand it. Here is a relevant quote:

Everything is vague to a degree you do not realize till you have tried to make it precise. – Bertrand Russell

If we do not make our models precise we cannot know if they are simple. As an example of this, the mental model for a behavior is a function of time. That's all. That is not only precise mathematically—it is also extremely simple and can be easily visualized. Every single function in Hareactive on behaviors can be understood based on this simple mental model.

So now back to Reactive Magic and Turbine -- I totally agree with all of your points, but what if we think about these things from a mental model perspective. A Value is like a cell in Excel. Its totally impure, but the abstraction clean and intuitive.

I'm not sure if the abstraction is clean. What does it mean to be "like a cell in Excel"? A cell in Excel is a piece of text. A Value isn't just a string? How do you explain what get, update and DerivedValue mean without talking about implementation details?

When you say "The input to counter is mixed with the output from the counter.", my thinking is "That's exactly whats happening!". We're constructing a machine using Values as an abstraction over things that change and wire all these pieces together as if they aren't changing. What make Reactive Magic so interesting to me is the mental model fits very comfortably in my head. I don't have to think about thing. I can just grab the Values I need, put them together, and it works!

What do you mean with "that's exactly what's happening"? To me you're constructing a machine that you put Values into. However, it wont treat at values the same. It will read from some of them and write to others. The machine itself doesn't indicate to which it will do what. You'll have to know about the internals of the machine to know. It doesn't seem like a nice machine to use.

On the other hand, a component in Turbine has one hole for input and another hole for output. This makes it easy to see what goes in and what goes out. To me that is not only a nicer mental model, it is also a nicer thing to code with.

I think it's very interesting to hear your thoughts on mental models. If I understand correctly what you value is the intuition in the model and how easy it is to wrap one's head around. That seems a bit different from my approach. Because I also what the model to be precise in a mathematical sense. I'd love to hear more about how you think about the mental model in Reactive Magic?

If you're interested in my angle you may want to hear this podcast with Conal Elliott where he talks about how he invented FRP based on an idea about giving precise and simple meaning to things. You may also be interested in this blog post of mine where I discuss some of the same things and "reinvent" FRP based on those principles.

@trusktr
Copy link

trusktr commented May 26, 2017

@ccorcos

I created this prototype instrument to explore a better mental model for creating music

❤️

I can't even figure out why we are yielding anything from the counterModel.

const app = go(function* () {
  const {
    count // get the end of the "count" output wire
  } = yield counter({ delta: Behavior.of(1) }); // of this component

  yield counter({ delta: count }); // and connect it to this component's input hole.
});

@trusktr
Copy link

trusktr commented May 26, 2017

Oh, you meant about the

  const count = yield sample(scan((n, m) => n + m, 0, changes));

Yeah, I'm not sure what that is either, I glazed over it, and the README doesn't really say? @paldepind Can you point to the explanation for those? https://github.com/funkia/hareactive#stateful-behaviors-work

@trusktr
Copy link

trusktr commented May 26, 2017

Whatever they do, I'm guessing the yield is for the same reason as I described: take the end of the output wire, then return it to be used as input somewhere else (and that the output wire just isn't from a UI component in this case).

@trusktr
Copy link

trusktr commented May 26, 2017

@paldepind Is that a good way to think about it? Or is there a another way?

@dmitriz
Copy link
Contributor

dmitriz commented May 26, 2017

@ccorcos @trusktr

I have put this probably simplest possible example to illustrate it here:
#48

It does not use any methods, so might be easier to understand.
Every yield "writes" a new dom node,
which all get concatenated together at the end.

Independently, the output is extracted and passed to the next component as argument.
That way there is no need to deal with any state outside,
which can cuts down the code a lot.
Just think of all those actions boilerplatte you would need to write with Redux instead. ;)

@ccorcos
Copy link

ccorcos commented May 31, 2017

IMO as long as a function is pure it is 100% declarative. It doesn't get more declarative just from being written in a point-free style.

I suppose. But even pure functions can have some imperative annoyances if they are composed together.

But, I would argue that mental models go hand in hand with math. Because math gives us a precise language.

Math definitely offers some guides for how to think about things. But it's not the only precise language. You can write very imperative code with in-place mutations that is very straightforward and precise.

One example of where I think math can start to convolute is that many people do not understand integrals and derivatives simply as summing up values and taking the difference between values. When you become very comfortable with math, that's what you eventually come to understand, but until then, you have to learn these awkward motions for moving numbers and symbols around for no apparent reason...

If I understand correctly what you value is the intuition in the model and how easy it is to wrap one's head around. That seems a bit different from my approach...

Exactly. Well I value both... But with Reactive Magic I decided to try out one extreme and found it to be quite interesting.

I'd love to hear more about how you think about the mental model in Reactive Magic?

Its hard to explain in a way. The goal is for the mental model to be totally obvious, the way a storyboard is totally obvious. Have you ever checked out Apparatus? Its a little clunky to use at times, but I think the mental model is totally obvious. That's the kind of thing I'm going for.

This might be a bit of a stretch, but one way I think about it is that I don't want to get bogged down writing the causality of one thing happening from beginning to end all at once. I want to be able to break things up into pieces the way I think about things. I want to be able to create contraints as I go without having to keep the big picture in my head or diligently creating the perfect abstractions around pure functions... Think about this in regards to Excel -- you can take a cell and derive it from any other cell. And you can keep on doing this until you have a very complicated system. And it doesnt really matter what order you do all of this in. Similarly with Apparatus...

If you're interested in my angle you may want to hear this podcast with Conal Elliott where he talks about how he invented FRP based on an idea about giving precise and simple meaning to things. You may also be interested in this blog post of mine where I discuss some of the same things and "reinvent" FRP based on those principles.

I'll check these out and get back to you. Thanks!

@ccorcos
Copy link

ccorcos commented May 31, 2017

@paldepind
Copy link
Member Author

@trusktr I think the "take the end of the wire" analogy is really good. It works very well for Component but not quite as well for Now. Does the explanation of Now in the Hareactive readme make sense to you? If you have any questions I'd love to hear them.

@ccorcos

I suppose. But even pure functions can have some imperative annoyances if they are composed together.

What do you mean? How can a pure function have imperative annoyances? To me, pure and imperative are mutually exclusive.

You write

I don't want to get bogged down writing the causality of one thing happening from beginning to end all at once.

And

Think about this in regards to Excel -- you can take a cell and derive it from any other cell. And you can keep on doing this until you have a very complicated system.

What is the difference? It seems the same to me. In Excel you begin by defining some cells in terms of other cells. Isn't that the same as writing the causality between things?

Btw, I've figured out a way to get something very similar to the automatic in Reactive Magic but in a way that is pure and fits in well with the FRP semantics. I'll be adding it soon to Hareactive.

link is broken

Sorry. GitHub turned it into a relative link. The correct link is http://vindum.io/blog/lets-reinvent-frp.

@ccorcos
Copy link

ccorcos commented Jun 6, 2017

How can a pure function have imperative annoyances?

Even with pure functions, if they aren't composed together well, then you end up with all these ridiculously named intermediary variables -- which is why I think point-free is more declarative.

const x = 1
const xPlus2 = add(2, x)
const xPlus2Times4 = times(4, xPlus2)
const y = minus(1, xPlus2Times4)

y = pipe([
  add(2),
  times(4), 
  minus(1),
])(x)

What is the difference? It seems the same to me. In Excel you begin by defining some cells in terms of other cells. Isn't that the same as writing the causality between things?

Hmm. Perhaps I didn't explain that well. When you have a Component in reactive-magic then the view function is "reactive" so that if you .get() any Values, then whenever those values update, the function will re-run. Its the same exact thing as constructing a stream in flyd except you don't have specify all the dependencies up-front. In a big nasty component, you might have all kinds of helper functions that get called in the view function. And what I find elegant about reactive-magic is that if you need to change some style based on whether or not the sidebar is open, you can simply .get() that value and everything just works. And imagine this function is used across many components in deep places -- you don't have to track down everywhere its called inside of every view function and add the sidebar state as a dependency of the view stream. And if you need to add a new dependency, you dont have to go through this tedious process all over again -- its basically just magic ;) So in terms of causality, I guess my point is that you dont need to know everything that causes a component to update before you write the function. And when you have a big complicated application with lots of different shared helper functions, this is incredibly convenient. For example, I work at Notion (let me know if you want a free subscription 😄). Its crazy how hard it is to coordinate so many things that are fundamentally impure like the contenteditable API, and I've found this approach to be very useful.

Btw, I've figured out a way to get something very similar to the automatic in Reactive Magic but in a way that is pure and fits in well with the FRP semantics. I'll be adding it soon to Hareactive.

Please, do tell!

@ccorcos
Copy link

ccorcos commented Jun 6, 2017

I read your blog post -- very nice explanation! I really like how you derived everything starting with the fundamental motivations.

A few things that immediately caught my eye:

  • you introduce the circle function with no indication of how works without breaking purity.
  • the way streams work is not performant. It will grow unbounded making .reduce slower and slower.
  • it appears you have to use requestAnimationFrame for the whole architecture to work. Something about this feels less elegant than simply reacting to an event when it happens. Otherwise, you're unnecessarily running through a loop when you dont have to.

What do you think? I'm going to look into Hareative more now and see what I can figure out.

@paldepind
Copy link
Member Author

paldepind commented Jun 7, 2017

@ccorcos

This reply got a bit long. Sorry.

Hmm. Perhaps I didn't explain that well. When you have a Component in reactive-magic then the view function is "reactive" so that if you .get() any Values, then whenever those values update, the function will re-run. Its the same exact thing as constructing a stream in flyd except you don't have specify all the dependencies up-front. In a big nasty component, you might have all kinds of helper functions that get called in the view function. And what I find elegant about reactive-magic is that if you need to change some style based on whether or not the sidebar is open, you can simply .get() that value and everything just works. And imagine this function is used across many components in deep places -- you don't have to track down everywhere its called inside of every view function and add the sidebar state as a dependency of the view stream. And if you need to add a new dependency, you dont have to go through this tedious process all over again -- its basically just magic ;) So in terms of causality, I guess my point is that you dont need to know everything that causes a component to update before you write the function. And when you have a big complicated application with lots of different shared helper functions, this is incredibly convenient.

Thank you for the explanation. I think I get it now 😄 You like how you can just use the stuff you need and changes will just propagate without you having to think about it. I can definitely see the convenience of such an approach.

How does the view get ahold of the sidebar-is-open state? Is it exported from a file so that anyone can simply grab it? If any code can just grab the stuff it wants to use doesn't it make it hard to figure out what the consequences of changing something might be? I'd be worried that the code might end up hard to understand and maintain? For instance, a might be used in b. I now want to change something in b and for this is makes sense to also change a. But, I had forgotten that c also used a and by changing a I break c.

I think with too many implicit dependencies code can be hard to understand. It sounds like it could give some of the same problems as in imperative programming where one variable may be mutated many places and where it gets hard to figure out exactly what happens to the variable.


I read your blog post -- very nice explanation! I really like how you derived everything starting with the fundamental motivations.

Thank you. I'm glad you liked it 😄. You make some very good points.

you introduce the circle function with no indication of how works without breaking purity.

I do try to give an explanation of it in the blog post. But, I guess it's not sufficient. circle is simply implemented as this pure function:

function circle(radius: number, x: number, y: number): Image {
  return [{ radius, x, y }];
}

The source code for the entire library and the examples are here btw. The circle and stack function is defined here.

the way streams work is not performant. It will grow unbounded making .reduce slower and slower.

Yes! That is absolutely correct and very well spotted 👍. Implementing pure FRP without such leaks is actually quite tricky. I've written a bit about it in this comment (the relevant part starts with "Now solves FRPs notorious problem ..."). There I try to explain why implmenting scan without leaks is tricky, compare various scan implementations, and show how the scan in Hareactive is both pure and memory safe. There is also an explanation in the Hareactive readme.

it appears you have to use requestAnimationFrame for the whole architecture to work. Something about this feels less elegant than simply reacting to an event when it happens. Otherwise, you're unnecessarily running through a loop when you dont have to.

Indeed. There are two approaches to implementing FRP: "push-driven" and "pull-driven". Push-driven is when you push events from the top down. I.e. what you describe. Pull-driven is when the state is computed from the bottom and up. The code in the blog post is "pull-driven". Pull-driven is easy to implement in a purely functional way which is why I did that in the blog post.

For the purpose of animations, pull-driven is actually very elegant. Note how most things in the examples in the blog post are changing continuously. For instance, the position of the ball changes arbitrarily often. How would you represent the movement of the ball as reacting to an event? How often should these events fire? 60 per second? 120 per second? Any number you choose would be arbitrary and inappropriate in some cases. The beauty of the pull approach is that we don't have to make that choice. We can simply pull the system in requestAnimationFrame and everything will smoothly follow the framerate of the user.

As you pointed out, the pull-driven approach also has a downside. If we're reacting to something that changes rarely and in response to user events then pulling every frame is totally inefficient. That is why Hareactive uses a combination of push and pull. For things that are event driven we use push and for things that "changes all the time" we use pull. In this way, we can get the best of both worlds 😄


Please, do tell!

I will 😄. I've added the function to Hareactive. It's currently called moment (we might rename it to derive or something else). It works a lot like DerivedValue in reactive-magic but is completely pure. I'll try an explain it below. Please let me know if the explanation doesn't make sense.

Semantically, behaviors are a function from time to a value. So, in theory, we could create a function with the following semantic:

const at = <A>(t: Time, b: Behavior<A>) => b(t);

However, implementing this in practice is impossible since users could then sample behaviors at any moment in time. Arbitrarily into the past or even in the future. But, if the at function where partially applied with a time value inside the library we could give users this partially applied at. That is the idea behind moment. Semantically moment is simply this:

const moment = (f) => (t) => f((behavior) => behavior(t));

It can be used like this:

const z = moment((at) => at(x) + at(y));

moment takes a function that takes a function of type <B>(b: Behavior<B>) => B. I.e. it gets a partially applied at. Conceptually, each time the function passed to moment is invoked it receives at partially applied to the current time. So everything is pure and the semantics are very simple.

In the above example, I'd probably still use lift as I've argued previously. But, I think moment can be super useful in cases where behaviors are nested or dependencies are dynamic. The discussion over in the Reactive Magic thread in the Flyd repo convinced me of that. Here is a simple example

const baz = moment((at) => at(booleanBehavior) ? at(foo) : at(bar);

In that example, the baz behavior would dynamically switch between listening to foo and bar depending on booleanBehavior. lift can't do that 😄. Another use case could be if you have a behavior of type Behavior<Behavior<number>[]> and you want to create a behavior of the sum of the changing numbers in the changing array. With moment you could simply do:

const totalSum = moment((at) => at(listOfNumbers).map(at).reduce((n, m) => n + m, 0);

I'm going to look into Hareative more now and see what I can figure out.

Let me know how it goes! I hold your opinions in high esteem and I really appreciate the great feedback you've already given us.

I work at Notion (let me know if you want a free subscription 😄).

I hope you enjoy it 🎉 I took a quick look and it seems interesting and gave a good impression. When I get the time (I currently have exams 😢) I'll take a close look. I currently use Trello.

@dmitriz
Copy link
Contributor

dmitriz commented Jun 8, 2017

@paldepind

const baz = moment((at) => at(booleanBehavior) ? at(foo) : at(bar);

In that example, the baz behavior would dynamically switch between listening to foo and bar depending on booleanBehavior. lift can't do that 😄

Would it be different from the one below?

const baz = lift((bool, f, b) => bool ? f : b, booleanBehavior, foo, bar)

@paldepind
Copy link
Member Author

@dmitriz

Would it be different from the one below?

Yeah. I wasn't clear enough about that. The difference is mostly an implementation detail that can give better performance. The lift code does the same. But the function to lift will be called whenever foo or bar changes. The version using moment figures out which behaviors are actually used at a given time. So when booleanBehavior is true it will not listen to changes from bar.

@ccorcos
Copy link

ccorcos commented Jun 13, 2017

How does the view get ahold of the sidebar-is-open state? Is it exported from a file so that anyone can simply grab it? If any code can just grab the stuff it wants to use doesn't it make it hard to figure out what the consequences of changing something might be? I'd be worried that the code might end up hard to understand and maintain? For instance, a might be used in b. I now want to change something in b and for this is makes sense to also change a. But, I had forgotten that c also used a and by changing a I break c.

The sidebar lives as a global value inside a World object. You're right that this started to get complicated and can become hard to understand, but I wonder if this is not always the case. Purity tends to come with extra verbosity which itself can hinder understanding. So what's important with reactive magic is to maintain clear semantics and abstractions around what something is or does. So in your example, if a, b, and c have a well-defined purpose, then if you change a for the sake of b, then its likely that you intended for c to change as well.

I read through the documentation again and its still hard for me to really grasp. I think whats missing for me is a tutorial that builds up a few entire examples app from scratch. That's what you started to do in your tutorial, but you never really showed you the circle function works and how you start everything up. Otherwise I'm just a little lost. I did something like that here building larger and larger abstractions, just for the sake of learning. I know there's a lot of complexity to hareactive and turbine, but I'm curious if you stripped everything away, what it would look like to build a simple counter like this but with the turbine architecture. Maybe you'd build just exactly like your blog post? But then I'd like to see some follow-up examples that try to explain in a minimal way how you deal with the shortcomings we discussed.

I'm really happy Reactive Magic was able to inspire you to write moment :)

Also, I'm working on this project grap (for lack of a better name) and I'm building it with Reactive Magic. Its still a work in progress, but there's already a decent amount of complexity and I think it might be an interesting experiment for you to test out with turbine. You can boot it up pretty easily (npm install && npm start), and for the most part, all it is right now is an infinite canvas where you can right click to create blocks, move blocks around, select multiple blocks, delete blocks, zoom the canvas with control+scroll (or pinch on a trackpad), and translate the canvas by scrolling. But I'm imagining doing this in a pure functional way and its hard because there is so much interdependent state. For example, when you click and drag a single block, you need to drag another other blocks that might be in selected as well. Thus the most intuitive way to deal with this is to have a global variable representing the selection. Otherwise, you'll be passing this selection around everywhere.

Let me know how it goes! I hold your opinions in high esteem and I really appreciate the great feedback you've already given us.

Thanks dude! You tend to write some really interesting libraries so I'm always happy to learn from you!

@paldepind
Copy link
Member Author

@ccorcos

Purity tends to come with extra verbosity which itself can hinder understanding.

I definitely agree that is what happens in many pure approaches. But it doesn't have to be that way. We are trying to avoid it in Turbine.

I read through the documentation again and its still hard for me to really grasp. I think whats missing for me is a tutorial that builds up a few entire examples app from scratch. That's what you started to do in your tutorial, but you never really showed you the circle function works and how you start everything up.

Thank you for the critique. We definitely need to improve the documentation. Is there anything specific that is tricky to grasp? If so please share it as it would be helpful to know what to focus on in the documentation 😄

I know there's a lot of complexity to hareactive and turbine, but I'm curious if you stripped everything away, what it would look like to build a simple counter like this but with the turbine architecture. Maybe you'd build just exactly like your blog post? But then I'd like to see some follow-up examples that try to explain in a minimal way how you deal with the shortcomings we discussed.

In a sense, there is some complexity. I think mostly due to our use of monads which is not that familiar to JS developers. But I think the complexity is appropriate. I.e. it solves real problems. I've compared it to callbacks vs. promises. Promises are more difficult to understand than plain callbacks. But, the complexity has the effect that code using promises ends up being simpler.

I'm really happy Reactive Magic was able to inspire you to write moment :)

Me too. It makes some patterns much nicer to code 👍

Also, I'm working on this project grap (for lack of a better name) and I'm building it with Reactive Magic. Its still a work in progress, but there's already a decent amount of complexity and I think it might be an interesting experiment for you to test out with turbine.

I gave it a spin. It looks like something that would be interesting to program with Turbine. I do have a lot of stuff on my plate though. But, where do you think would be an appropriate place to start? Something that's particularly challengin?

@ccorcos
Copy link

ccorcos commented Jul 6, 2017

@paldepind I think I came up with a good example motivating my divergence from pure functional code.

Suppose you have a gigantic application represented as a tree of components. Suppose you want to share some global state amongst a few of these components deep inside the tree. A realistic example of something like this is a button in a page that closes a sidebar, or a color theme.

  • In a pure functional application, you have no choice but to thread that state through the arguments all of the top-level components to the components that need them. To alleviate some pain, you might wrap up all this state in an object and pass the entire object down to all children components. But that means that some components are going receive state they don't need causing them to re-render unnecessarily. Needless to say, this can be quite frustrating.

  • With reactive magic, all you need to do is .get() that global variable wherever you want to use it and it just works! Its magic! ✨

stevekrouse pushed a commit to futureofcoding/futureofcoding.org that referenced this issue Dec 12, 2018
* TOC
{: toc }

### Last week

Last week was productive in terms of publishing two podcast and recording a third. I also worked a bit freelance. I only need to do a dozen hours freelance at this point in conjunction with the money from the podcast sponsorship to make ends meet. Eventually with more sponsorship and Patreon, maybe I can slowly lower the number of hours freelance towards zero. 

### Github Issues / Projects

I also started my Dec Regroup Projects, including moving my todos to Github Issues / Github Projects. I now have three Github projects:

* [To Do](https://github.com/stevekrouse/futureofcoding.org/projects/3) is my pipeline of tasks
* [To Research](https://github.com/stevekrouse/futureofcoding.org/projects/2) is my pipeline of links and topics
* [My podcast pipeline](https://github.com/stevekrouse/futureofcoding.org/projects/1) is where I organize my guests

I'm a bit worried about how this new system will interact with this log. While it felt silly to copy and paste todo lists over and over in this log, it's also a bummer doing things in my new system doesn't show up in here. This log is supposed to be a log of all my work on this project, so I'd love to pull in Github issues activity somehow... Now that I say it "out loud" I wonder if I can do that automatically... For now though, I may just copy and paste some things from Github issues here when relevant.

### Jekyll refactoring

I get really fustrated with Jekyll sometimes. My main issues were:

* I was having trouble with importing page snipets 
* I wanted to be able to preview my site locally with all the CSS. (Github Pages had some setting I didn't have set.)

After many annoying hours I was able to make both of these things work. My main next big issue is a navbar on all my pages of: 

* Home
* About
* Podcast
* Log
* Community
* Contact
* Fork

Of course new styles and logo would be nice as well, and maybe a commenting system, but it's not top priority.

And to be honest, starting the Patreon is bigger priority than all of these but I'm a bit scared so I keep procrastinating...

### Turbine 7GUIs!!

After doing 3 hours of emails yesterday (I was sick towards the end of last week, so I took Thursday off and didn't do my emails Friday, sat, or Sun), I spent a few hours messing with Turbine, and then ~7 hours today in it. So fun! Despite being rough around the edges, it feels like there's really something wonderful here - I'm very impressed with the design. Polishing this is a million times better than having to start from scratch.

To take it for a spin, I've been doing the 7GUIs tasks in Turbine, and whenever I get stuck or find a bug, I write it down, and sometimes open an issue. I'm tracking all the progress in [this Github issue](#96), which I will copy and paste here:

#### Issue opened

* [typescript issue on starter kit](funkia/turbine-starter#2)
* [combining model and view](funkia/turbine#81)
* [devtools](funkia/turbine#82)
* [a single page documentation cheatsheet](funkia/turbine#84) 
* [Text input bug](funkia/turbine#86)
* [versioned documentation and various styles of `output`](funkia/turbine#85)

#### To open issue

##### Issues using `lift`:

1. Do I import it from `jabz` and do `lift(f, b1, b2)`? Or I use it as `b1.lift(f, b2)`?
2. It doesn't work in the view with error `Attempt to sample non-replaced placeholder`
3. Doesn't seem to work for 4 behaviors but the documentation says "You can also combine in this fashion any number of behaviors"

##### Constructors need to be documented across all projects

* I need the constant behaviors, streams, `IO.of` to be easier to get at than going to the tests to find them.

##### filterApply bug

`filterApply(Behavior.of(undefined).map(() => () => true), stream).log()` upon an event from `stream` errors: `predicate.at(...) is not a function`. If you replaced `undefined` with `1`, the error goes away.

##### * `time.log()` does nothing

It should at least error if it's not going to show you anything, but I might prefer it to show me all the milliseconds.

##### when doesn't log correctly

`when(Behavior.of(true)).log()` errors `Cannot read property 'Symbol(Symbol.iterator)' of undefined` but `yield sample(when(Behavior.of(true)).map(x => console.log(x)))` outputs an `OfFuture` value

#### To look into more

* What is the `moment` function (described [here](funkia/turbine#51 (comment)))?

* Use [`loop`](https://github.com/funkia/turbine#loop) successfully without `this.source.addListener is not a function` error

* What's the difference between `go` and `fgo`? Also, the placement of arguments here is confusing:

```javascript
const tempView = ({ c, f }) =>
  go(function*() {
```

* When you can do infix dot vs prefix

* I want a dummy model so I can see the view while I mess with stuff and not get `The generator function never yielded a monad and no monad was specified.` without

```javascript
const tempModel = fgo(function* ({  }) {
  yield Now.of(10)
});
```

* Need to call the function created with `performStream` or `performStreamLatest` (I don't get the difference): `performStreamLatest(book_.map(() => withEffects(() => alert('hi'))()))`

* It's also quite annoying to keep the output and inputs in sync always. 

### Priorities 12/12/18 - 12/21/18

* freelance 10 hours
* Patreon   20 hours
* Turbine   20 hours
  * finish 7GUIs
  * work on various issues

### Push to vacation or 2019

* publish Tudor and Vlad episodes
* p4 stuff
* website design and improvements
@stevekrouse stevekrouse mentioned this issue Jan 29, 2019
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

4 participants