intercellular / cell

A self-driving web app framework
https://www.celljs.org
MIT License
1.5k stars 93 forks source link

Cell.js Long Term Plan and Design Decisions #111

Open gliechtenstein opened 7 years ago

gliechtenstein commented 7 years ago

What is Cell, really?

Here's what Cell is, in one sentence:

Cell is a function that constructs a decentralized structure of autonomous HTML nodes from a blueprint.

But this post is about the future of Cell, so just for the sake of discussion let me switch out the "HTML nodes" part with a more generic term "objects":

Cell is a function that constructs a decentralized structure of autonomous objects from a blueprint.

Let's step back and think about this for a moment. An HTML node is nothing more than a special purpose object that has its own distinct properties. And all Cell does is create autonomous versions of these objects using the Web API.

If we dive further into this line of thinking, theoretically we should be able to do the same for any kind of object. So.. does this mean we can apply Cell.js on any other types of objects today?

Not today. But I think it should be possible. We just need to successfully decouple the DOM part out from the core. And when that becomes possible, we should be able to build not just frontend web apps, but all kinds of other applications using the cell approach, such as:

The point is, Cell potentially can evolve into a general purpose "autonomous engine" that can be injected into any object natively, just like what it does today with the DOM.

How does this idea translate to the current implementation?

The reason I mention all this is because every aspect of the library implementation and design was influenced by this potential factor. Let me explain.

Attribute Naming Conventions

1. $type

I went through multiple iterations for this attribute before deciding on $type. Here are some I tried: $tag, $node, $element, $el, $e, $c.

In the end I chose $type because it is an abstract term. It can be applied to pretty much any context, and intuitive enough that anyone would get it with no prior knowledge.

The only case I'm not fully satisfied is when using it to describe <input type='text'> because it looks like {$type: "input", type: "text"} (two "type"s), but really this is the only case and I think the benefit outweighs the cost.

2. $components

I went through multiple options before deciding on $components. The strongest contender for this attribute was $children.

The reason I dropped $children was because the term implies that there’s a top-down tree-like hierarchy.

This is true for the DOM tree, but it may not be the case for other types of potential usage. For example a different implementation in a parallel universe may have a “previous/next” relationship instead of a “parent/child” relationship. It could also be a many-to-many relationship instead of a tree structure.

$components on the other hand is more abstract. The term can also imply an ownership relationship but is not as explicit. It's used to describe an encapsulated environment and doesn't necessarily imply a parent/child relationship.

This is why I picked $components.

3. $cell

This attribute is critical for keeping cells completely autonomous.

The “$cell” attribute is used to indicate that its parent object should turn into an HTML node.

3.1. Why not merge $cell and $type?

Some people raise the question: "why not just merge $cell and $type to describe the type of a node? That way we get rid of one attribute and it's cleaner".

Well it's a bit complicated. The $cell attribute is used as an explicit marker for cell construction. For example let's say we have

Li = {
  $type: "li",
  $text: "Item"
}
OrderedList = {
  $cell: true,
  $type: "ol",
  $components: [Li, Li, Li]
}

Because the OrderedList is the only object that contains the $cell attribute, that's the only one that turns into an HTML node.

The end result looks like this:

<ol>
  <li>Item</li>
  <li>Item</li>
  <li>Item</li>
</ol>

Granted, Li does eventually get referenced inside $components so they do get incorporated into the DOM as one of OrderedList's components, but that's exactly the point.

By NOT including the $cell attribute, we are able to define all these "cell candidates" such as Li without having them automatically added to the DOM. The $cell attribute lets us dynamically introduce cells when we need them.

To make the point clear, let's see what happens if we did have the $cell attribute on the Li variable, like this:

Li = {
  $cell: true,
  $type: "li",
  $text: "Item"
}
OrderedList = {
  $cell: true,
  $type: "ol",
  $components: [Li, Li, Li]
}

The end result would have been:

<li>Item</li>
<ol>
  <li>Item</li>
  <li>Item</li>
  <li>Item</li>
</ol>

which is not what we are expecting.

This is why we need an explicit marker ($cell) to indicate whether an object needs to be turned into an HTML element or not. In many cases we don't want them to be immediately added but dynamically added later, sort of like how we use HTML templates.

If you have a better idea to get around all these issues simultaneously, please feel free to share. It's always best to have less attributes than more.

3.2. Why not just use a factory method to explicitly create cells, and get rid of the $cell attribute?

This feature used to exist but I took it out right before I released the library publicly. It used to look something like this:

Cell.$build({
  $type: "div",
  $text: "hi"
})

It's not that I think having a factory method is a bad idea. In fact we can add it if we really need to.

The real reason I took it out was because I believe the main way to interact with the library should be the declarative approach. If something doesn't work with the declarative approach, we should make it work instead of trying to solve that problem with a factory method, because that's not solving the problem fundamentally.

Even if we find out that it's impossible to have a 100% coverage with the declarative approach, this doesn't mean we should completely switch to the factory method approach and get rid of the $cell attribute.

The primary approach to using Cell should always be the declarative approach (and maybe the factory method can be used in edge cases where this is really not possible)

Let me explain why with an example web app:

<html>
<script src="https://www.celljs.org/cell.js"></script>
<script src="https://walk.com/gene.js"></script>
<script src="https://eat.com/gene.js"></script>
<script src="https://socialize.com/gene.js"></script>
<script src="https://appearance.com/gene.js"></script>
<script src="https://inheritance.com/gene.js"></script>
<script src="https://think.com/gene.js"></script>
</html>

Each script is loaded from a different source and each has its own distinct feature. One may describe a certain behavior, one may describe the data, one may function as a catalyst that composes two other objects to create a new object with a completely different behavior. The point is, none of them explicitly depend on another.

And when they all come together, they somehow emerge into an app.

The important part is that these scripts have no knowldege of one another.

Why is this important? Let's say we're trying to build an emergent app that shapeshifts itself depending on context. Here's how it could work (Please use your imagination because these types of apps don't exist yet, which is what Cell was built for):

  1. The app checks the geolocation of a user
  2. Depending on the location, it queries some 3rd party service to see what kind of apps are available
  3. Depending on the response it receives, it queries another 3rd party service which responds with an adequate app template.
  4. The app also takes into account locally stored data that is private to only the user.
  5. All of these are composed together to construct a completely unique app. For example, if you're in your neighborhood the app may turn into a restaurant review app, but if you're away from home traveling, then it could turn into a travel app.
  6. The key to all this is that each of these modules is completely independent and has no knowledge of each other. These data/application are automatically resolved, delivered, and constructed from multiple different parties, in realtime.

In cases like this, you can't use a factory method because factory method only works when you know what your app will look like when you're writing your app. In this particular case you don't know until everything is resolved and constructed.

This type of app may sound like a sci-fi story but it is indeed possible with Cell, and is what Cell was designed for. And to be able to write apps like this, we need a completely decentralized way of writing autonomous modules.

This is why I think we should be almost paranoid about getting the declarative approach to work, which is why the factory method is not the solution.

p.s. I'm not saying Cell is only for these weird cases. It's a perfectly fine framework for building all the normal web apps we use every day. My point is we can do that AND much more if this framework was completely declarative.

Variables

Does Cell modify the DOM directly?

One of the strengths of Cell is the syntax is its intuitive syntax. To set a state using some framework API method like setState(), you simply assign it directly to the this context, like this:

button = {
  $type: "button",
  $text: "Click me",
  onclick: function(e){
    this.$text = "Clicked";
  }
}

A lot of people look at this usage and immediately think they're directly storing everything on the HTML element. But rest assured, that's not how it works. Cell keeps an internal proxy object that makes it look as though you're directly accessing the DOM but in fact you're only touching that proxy object.

Why is the "no prefix" option used for DOM attributes?

Cell has 3 classes of variables:

Let me explain how this system came to be:

The reason Cell plays so well with any other web technology is because it does not mess with DOM attributes.

Many things can go wrong if we don't do it this way because we have no control over these custom objects (in this case HTML elements) Cell attaches to. For example, if we decide to use the $ prefix to refer to the dom attributes, we can never be sure what this will result in in the future when the web standard changes or browser implementations change.

This can especially become a very serious issue when we consider the fact that someday Cell may evolve into a general purpose engine for constructing any kind of autonomous object. We need to minimize the complexity as much as possible if we want to achieve flexibility and extensibility.

Anyway, that's why the native DOM attributes are treated as first citizens and no prefix is used to refer to them. Native attributes should be left untouched to reduce complexity.

What's up with all the cell biology reference in the code?

If you don't understand what this question means or if you haven't read through the source code, you can ignore this. This is for those of you who have read through the code and thinking about tweaking the code (and hopefully contributing!)

First thing you'll notice when you look at the source code is there’s a lot of biology stuff going on (genotype, phenotype, gene, nucleus, etc.) instead of the typical programming keywords like model, view, controller, state, props, etc. we are familiar with.

Here's why:

  1. Language shapes how we think
  2. If you are reading the source, I presume you are reading it because you want to understand it better.
  3. In that case, thinking about it from a biological perspective will help you understand it much better.
  4. Cell's long term goal is to evolve into a more general purpose engine, so it doesn't make sense to use concepts constrained to frontend web development.

But here's the real reason: I named them this way out of 100% practical reasons. During development I got stuck multiple times. I found it very difficult to structure and visualize the codebase because cell's architecture doesn't really fit into any existing "web app framework" paradigm. For example, during the early days I used terms like "Model", "View", and "Controller", and these terms got me almost halfway there, but after that they did more harm than good because a lot of the critical concepts in Cell were hard to categorize using web development concepts, and these web development way of thinking limited my way of thinking.

One day I decided to try to organize the code purely based on cell analogy, because after all, that's where I got the inspiration from. And once I did that everything became crystal clear and I could finish the library.

Lastly, here's a question: If you could pick from:

  1. building a web app framework that acts like a cell
  2. or a cell that can act like a web app framework

What would you build?

I think it's much more powerful to try to build a simulated cell that can act like a web app framework (and more), because it can be applied to many different contexts. And to achieve this I think we need to think like a biologist instead of a computer scientist.

devsnek commented 7 years ago

I really like the idea of separating the html elements from cell. I might look into this...

NxtChg commented 7 years ago

microservices, artificial neural networks, decentralized applications, blockchain...

What the?.. Architecture Astronaut much?

NxtChg commented 7 years ago

This is so idiotic, I don't even know where to begin!

You've created a tiny library, which core feature is adding some code to DOM elements to match JS variables.

And you're gonna sacrifice real, practical usability for the sake of some delusional grandiose plans of world domination?!! With what?!! Your 500 lines of code?!!

What kind of mega-value will you add to all those things (AI networks, ffs?! Really?), which don't even have any DOM elements?

What is your grand idea? "Using JSON to describe The Structure of The Universe"?..

Jesus... This is so delusional, you should go and do an MRI scan to see if you have some sort of a tumor that ate your brain...

Somebody, for the love of god, clone those 500 lines and do it right!

gliechtenstein commented 7 years ago

What is your grand idea? "Using JSON to describe The Structure of The Universe"?..

Yes! Totally spot on! Bravo :) 🎉 🎉 Check out my other project https://github.com/Jasonette/JASONETTE-iOS

Just to be clear, Cell and Jasonette are completely separate projects, but will probably prove to have synergistic effect going forward, because they share similar design principles :)

leeoniya commented 7 years ago

while i disagree with @NxtChg's demeanor, i do generally agree with his feedback.

Generalization, categorization and structural uniformity has been a goal of all engineers ever. "Everything is a file" [1] (Unix), "Everything is an object" (OOP), "Everything is a function/immutable" (FP), "Everything is a tree", etc..

While many classes of problems can be represented via these structures, it's not always the case. A graph is not a tree, only i/o stuff (sockets, files, endpoints) can logically be grouped into "files", a value is not a function, hardware is not immutable. Trees will have very sub-optimal performance in many use-cases that can technically look like trees if you squint hard enough.

An overly general declarative solution for all problems will be a pretty bad case of shitty "wtf" usability, shitty performance, or both due to the underlying truth that the abstractions mask. You will not be able to predict the needs of all domains into the future. At some point, the abstractions will simply cease to make sense, or become so deep as to be incomprehensibly translatable for the target audience.

Declarative solutions typically only work well when restricted to a given domain. You need imperative building blocks to really be generic. I think a good demo of this is e.g. S (FP) [2] and the derived surplus (UI) [3].

It's amazing how awesome a domain-specific API can be and how unusable an overly-generic one can be.

https://en.wikipedia.org/wiki/Unix_philosophy#Do_One_Thing_and_Do_It_Well

[1] https://en.wikipedia.org/wiki/Everything_is_a_file [2] https://github.com/adamhaile/S [3] https://github.com/adamhaile/surplus

gliechtenstein commented 7 years ago

@leeoniya thanks for sharing your thoughts. I was hesitant to write this post because I have no intention of dropping everything and just start working on this "generalization" problem right now, and didn't want to mislead people. The top priority is improving Cell as a web app framework. But I took some time to write this post to address the suggestions made over at https://github.com/intercellular/cell/issues/23

To be clear I don't think there's any problem with cell's usability as is, at least in the sense brought up in the linked issue.

And at the end of the day it's a bad idea to just overhaul the entire API of a library that's already out there just because of a subjective opinion.

I do agree with all of what you say, and I have no plan of making this library worse just for some philosophical ideal, so I totally welcome your feedback if you have specific thoughts and ideas on the current implementation. I would love to discuss.

If there are parts of the code today you think are causing problems because it's too declarative, please point out and we can discuss and figure out a solution. Thanks.

gliechtenstein commented 7 years ago

@devsnek if you do get around to making any progress please share! 👍

devsnek commented 7 years ago

@gliechtenstein will do (p.s. can you look at #36 :smile:)

jaimegomez commented 7 years ago

@gliechtenstein For what is worth, I deeply enjoyed this post, it's the kind of thing I spend most of my time thinking about and trying to materialize in different ways in all my projects. So thanks for sharing your thoughts, even though I fully realize how for practical people this is all nonsense 😆

piranna commented 7 years ago

Good post, it should be put in the project docs like GENESIS.md or so.

Now I understand the point on the declarative way and the $cell atribute and I understood it, but I still disagree with the global context polution and the automagical way of registering the apps. I like the idea of the emergent behaviour, but It can also be done by calling each script to the factory function from their script themselves, so there should not be any problem here. I'm not saying about removing the declarative way, but instead allow an implicit way to register them instead of only an implicit one.

devsnek commented 7 years ago

you can explicitly render an object by calling document.body.$build(cell object)

Subnote: you can render children into anything on the page by calling <that element>.$build(cell object)

piranna commented 7 years ago

you can explicitly render an object by calling document.body.$build(cell object)

I know I can render any object using that, but my concern is about it would need the document.body object exists first, that for scripts at <head> I'm not sure if it's the fact, and would need to wait to window.Load as explained at https://github.com/intercellular/cell/issues/12. I think the solution proposed there to add a Cell() function would be good, internally checking the DOMContentLoaded event and injecting the nodes when it's ready.

devsnek commented 7 years ago

we could go along with the new node api and just attach that exports object to window named Cell but global pollution is annoying

gliechtenstein commented 7 years ago

@jaimegomez @piranna thanks for the feedback guys, I'll try to incorporate parts of this post to the repo as a file somehow.

As for the explicit instantiation topic, let me think about it a bit more today and get back to you, maybe there's a clever way to do this while making as little compromise as possible.

In the meantime if anyone comes up with a good idea on this, please feel free to share.

astritzinger commented 7 years ago

Thanks for the brilliant ideas of Cell that seem to be implemented just the right way. Many of your thoughts have been in my mind too, but ... I small complain I want to state, is the redefinition of the semantics of this. Consider the use another special variabe like $self, $context, $branch, etc.

minecrawler commented 7 years ago

I like the idea of cell (and I rarely like a library or framework!) and I might give it a try in one of my projects (Cordova App), pinch it a little to fit my idea of what it should do and then come back here to complain :D

However, as nice and little as it is at the moment, please do not try to go for your "grand schemes" just like that. I rather have a small, focused library than a monster, which can do many things, but is good at nothing, because of compromises. Also, I think this should stay a JS library, and I can promise you that calculation-intensive stuff, like AI, will not move to JS anytime soon.

Rather than mutating cell to something strange, use the idea and principles behind it to build similar libraries for your other use-cases. Then you can pick the best language and analogies and the whole experience will be superior in the end.


Well, as for why I think this library might be great: I have the chance to keep refs to my nodes, which makes separation and usage awesome! Don't destroy that :)

// nodeA.js
window.myApp.nodes.nodeA = { $cell: true, $type: 'input', /* ... */ };
// nodeB.js
window.myApp.nodes.nodeB = { $cell: true, $type: 'button', /* ... */ };
// fooLogic.js
window.myApp.nodes.nodeB.onClick = function() {
  sendToServer(window.myApp.nodes.nodeA.$text);
};
<!DOCTYPE HTML>
<!-- WHATWG minimal notation applies -->
<script>
window.myApp = {
  nodes: {},
};
</script>
<script src="nodeA.js"></script>
<script src="nodeB.js"></script>
<script src="fooLogic.js"></script>
<script>
const ele = window.myApp.nodes;
</script>

* untested, will come back with results.

idkjs commented 7 years ago

For what its worth, I'm a totally rookie programmer, and all the stuff out there is so hard for me to grok sometimes. I quite literally have a page in one my notebooks that sketches out this exact same thing, except I used the most ubiquitous elements from the periodic table of elements and a conceptual understanding of how they build biology. I was probably watching to much Neil Tyson-Degrasse and Cosmos, but nonetheless, it occurred to me that patterns repeat and that the universal pattern of construction of organisms from the elements in the periodic table of elements hit me hard. I'm really glad you did this and would love to be of help to you if that is possible.

gliechtenstein commented 7 years ago

@idkjs great to hear! Contributing to the library is helpful, and also just sharing examples is also super helpful, so please feel free to keep sharing anything you build with Cell. And feel free to contribute wherever and whenever it makes sense! Any type of contribution is welcome :)