HN user

jeremiep

1,816 karma

I enjoy hacking languages, game engines as well as new technology and have the chance to do it for a living.

Posts7
Comments657
View on HN

I find D to be a good tradeoff between C++ and Rust. I can't stand not having compile-time evaluation, code generation and reflection in Rust and C++ is just too slow to iterate with.

because they make our work easier

This seems to be my biggest pet peeve with the JS ecosystem as a whole; people chase things that are easy to learn rather than simple to reason about once learned and the result is almost always a convoluted mess of a codebase, regardless of the project.

I used jQuery, then Underscore, and later lodash; now I'm using ClojureScript and wondering why I ever could enjoy any of those crippled FP alternatives in the past.

"Someone who doesn't know about X will create its own worse version" is true, but it also applies to you and everyone else :) We don't know what we don't know (thats meta-ignorance) and we tend to assume what we do know is the state of the art, it quite usually is very far from it!

I'm perfectly happy in ClojureScript to be skipping TypeScript and most of the JS ecosystem :)

Not everything on the web has to use the normal stacks. I used TypeScript in the past but didn't enjoy the complexity of most JS libs, even with typings. Clojure was a breath of fresh air, and still is.

I see your point, thanks for the precision. I can agree perpetuated inequality does give kids a huge head start.

Taxation is already high for the rich, but the top bracket is usually quite low. I don't think the solution is more taxes, but more tax brackets; they should scale to accommodate the extra rich.

Its not like the poor is going down, they're also going up but not nearly as fast. Almost all of our poor people are rich by global standards is an argument I hear often. Most poor also end up middle class as they grow up, take responsibility and contribute to society is another I hear.

I lived in poverty for years after dropping out of college, sometimes with roommates that were way beyond toxic, worked multiple jobs 70 hours a week just to pay rent and food and still ended up middle class with a job I love; I've been through hell to find heaven. What I learned on the way I now use every day, its made me a stronger and better person and I can now help others do the same.

If someone had given me what I have today, just for the sake of equality, I would not have learned responsibility, discipline, I would hardly have developed most of the skills I now have and probably would've lost all of it by now. I would basically still be an angsty teen in an adult's body, which is what kept me poor in the first place.

Talks from Mike Acton and Scott Meyers, specifically "Data-Driven Development" and "CPU Caches and why you should care" respectively.

I forgot exactly where I got that number, but it's been a pretty good metric so far.

In a nutshell; the compiler is great a micro-optimizations and absolutely terrible at macro-optimizations. The former will get you a few percent of perf boosts while the later usually results in orders of magnitudes of performance gains.

Its near impossible to apply macro-optimizations at the end of a project without massive refactors.

When you realize the compiler's optimizations only account for about 10% of the total program's performance you find that the other 90% is entirely up to the programmer.

Architecture, data structures, batching operations, memory locality, and a bunch of other metrics are all concepts the compiler can't really help you with whatsoever and they have a much larger impact on performance than the 10% the compiler is actually able to optimize.

The problem is that either programmers don't care, or they can't make the distinction between premature optimizations and architecture planning.

But that is precisely how undo is currently implemented most of the time. Its a series of actions where you know how to undo the side-effects, or you know it was a destructive update and you just skip the undo of that step.

Where I have issues with that is it usually feels like an afterthought rather than a first-class feature.

If you instead model your application after an event log then your current state is just a fold over these events. You have the same list of actions you previously had to manage undo, but now its positioned at the very center of the architecture.

And then you start seeing emergent features of that design:

- you can time travel the UI's state, which is probably the best debugging tool you can have for UIs.

- you can replay the event log over fixed application code, which greatly decrease iteration times.

- it moves logic outside of the UI components, making the app easier to unit test.

- you're storing a sequence of intents rather than results; its effectively free to audit such a design, its convenient to report the last X actions performed with a bug report and more precise than asking the user what they were doing.

The list goes on and on. I believe most of the complexity in applications is generated from the fact there is very little synergy between the different features of these applications. If everything requires a different abstraction and set of indirections, then you're just accreting complexity over time.

If you have a design where features can emerge from it, its usually a safe-bet to say you're on to something :)

Vue Native 8 years ago

but probably not practical in reality.

Thats how I've been building UIs for the last few years. `re-frame` in ClojureScript for one is a fantastic framework built on top of these concepts.

I don't see the point of having a debate about the merits of functional programming vs. OOP in the middle of your post

I made that distinction to highlight the differences between retained-mode UIs versus immediate-mode UIs. There are more similarities to OOP vs FP than differences here.

Think of it this way; the application itself already contains all the state it needs. A retained-mode UI will deep copy that state into the UI components while an immediate-mode UI will make the UI state a function of the application state. There is huge value in doing that because it dramatically simplifies the UI stack, this translates into shorter iteration times, simpler reasoning and less bugs. Its no good thinking about higher level tooling if the fundamental API is overly complex.

unless you build a system that's easy to understand on that end as well

We shouldn't care about whether something is easy or difficult; we're only going to learn it once after all. We should instead care about whether its simple or complex, because that is a direct function of our ability to reason about things once they are learned. Most things that are easy to learn generate more complexity than simplicity and I believe this is harmful in the long term.

I don't think it's unreasonable to expect that the low-level rendering engine (ie. an implementation in UIKit) maintains some level of UI state.

You're right, its perfectly reasonable and precisely what React does under the hood. Where I disagree is that I don't want the cached UI state to leak into the components. Just like the current state of the DOM is irrelevant when rendering the virtual DOM. Its purely an optimization mechanism and one which should be allowed to evolve independently from the higher level components.

Things generally aren't immutable

Then why are so many things moving towards immutability? Modern video game renderers push really hard to be stateless, to be completely decoupled from the game threads, to not leak cached state into entities and a bunch of other FP-inspired concepts.

The end result is incredibly more parallelism. The rendering pipelines are much simpler and performant. There are quite a few advantages to this approach. It also generates optimal memory layouts and accesses, these are simply emerging from such a design. Its much easier to have linear memory accesses if you constantly recreate the display lists in fresh memory. Updating a state tree is much more complex than creating a display list and it greatly cripples the potential optimizations.

higher level libraries can easily remove the pain of UI state management

That is not the feeling I get when I look at the average React project; most components use getState/setState, need to touch the virtual DOM for almost every operation, are needlessly maintaining two views of the same state (app and UI) and whatnot. Or they go full Functional Components and the result is a huge stack of component layers from hell.

It seems to me the appropriate abstraction is build the protocol around a UI state tree

I believe this is fundamentally wrong; the appropriate abstraction should be built around the application state. You can derive UI state from that, and doing so liberates you from having to maintain the UI state independently.

Vue Native 8 years ago

A render list would be more efficient than a tree, simply because you're not jumping left and right in RAM with cache misses on every jump.

A hashmap of nodes on screen is still very high level; you're basically replacing the DOM with data, yet keeping the exact same structure. But now you lose static types and everything is slower.

Redrawing doesn't happen at the node level either; that would be terribly inefficient. Things are instead batched together.

You don't have to compile your styles to CSS currently; just write them as JS objects and call the CSS constructors yourself from code. You'll quickly find its not productive for most styling.

I simply don't believe the vast majority of developers to be able to properly handle rendering at that level. No offence but all your points about low-level rendering is not how it actually works. Its incredibly easy to fool yourself into thinking you wrote a well-designed, efficient piece of code and learn years later every single piece of it was far from optimal.

Thats why we have layers of abstractions.

Vue Native 8 years ago

This assumes the web rendering context is the proper abstraction to build application UIs. Its not. Its too complex and object-oriented to be efficient. It was intended for documents and is a general pain to work with for applications.

For one, there's a lot to be learned from immediate-mode UIs (I'm thinking Dear IMGUI, not Unity3D's GUI); ie state inside components generates complexity, which is a bad thing. Your views should be nothing more than a pure FSM transforming app state to UI directives. IMGUIs have a ton of tricks to simplify UI development. With these in mind, FP becomes a much more interesting paradigm than OOP for this. The same logic applies whether you render the entire UI 60 times a second or you cache the previous render to only do the minimum amount of updates.

A good abstraction is one where features can emerge from its design. For example you rarely have to test your views when they're just a simple pure transform. You don't need an entire framework and command-patterns to implement undo when your app state is immutable and modelled as a sequence of actions. With each emergent feature you remove the need for tons of code and tooling; that yields agility.

I still believe most web application are much more complex than they should be, especially on the UI level, regardless of the framework used. And until we solve that problem, I don't think the web stack is the way to go for native. We're just replacing one set of problems for another one.

I strongly believe we shouldn't make things easier, but simpler.

Anything where your app state is modelled as a series of actions over immutable data, undo and replay will be essentially free to implement :)

I feel React's best use case is as a view backend in micro-frameworks like re-frame in ClojureScript.

React only gets complicated when you use states and mutable props, or when logic is put inside components.

As a view backend it just becomes a FSM translating app state into pure views and nothing more.

Same, no way we're using a fork while the main project is still actively maintained and putting out great features. The added value of built-in SSL doesn't even justify losing half the value of using the main project.

What guarantees? Nothing prevents a function receiving a nursery from not using it. Unless the language can enforce it you don't really gain anything valuable over returning a Promise, except more complex code that doesn't compose.

Much like you know that control flow will come back to the function you're reading after calling another function.

What about continuations? Exceptions? Aborts? setjmp()? I can think of many cases where control doesn't return to the caller that are perfectly valid.

Basically, either the code is so simple theres obviously no bugs, or the code is so complex theres no obvious bugs. I feel a nursery is closer to the later than the former.

I agree, but I still wouldn't call a nursery an abstraction; its an indirection, and a mutable one at that. I also disagree with the goto analogy; concurrency and control flow are two distinct things, they have much more differences than similarities.

Implementing a promise as a monad will yield all the same benefits while also keeping the ability to compose and be immutable; and then you have an abstraction and the result is simple.

I agree restrictions make the code better, this just isn't one of these cases to me.

Agreed, I'd rather have a few simple constructs, especially if they compose.

Still, there's value in unification and generality, at least when the result is simple, even if that covers only 80% of cases.

For example you generally run a lot more composable tasks than actual threads and syncs. You get a general, and powerful abstraction for the general case yet threads are still there for more exotic needs.

This can change if OSes start optimizing for the proposed convention.

What conventions? Its not realistic to assume the world will change to fit your views of software :p

At least in C/C++ it doesn't.

Sure does; it adds more build targets, gets you to maintain shared code across executables, and plan deployment for multiple executables instead of one. Thats all before even coding the support for that.

Why not?

Because these depend on shared memory and ownership transfer for performance; you'll drastically drop performance just for the sake of isolation.

No reason for it to stay that way.

I will literally stop using the web if pages can spawn processes :)

I won't pretend to know all those words :P

Hehe, basically immutability makes it so nobody can mutate data, thus making it safe to be shared across threads. Uniqueness will transfer ownership such that only one thread has references to a mutable piece of memory at any time. Deterministic parallelism means its impossible to have race conditions or deadlocks.

I just think the article's proposal has some merit and we should consider it.

Agreed, I'm still having a hard time seeing it however :p