HNHacker News
TopNewBestAskShowJobs

grayrest

1,959 karma · joined May 10, 2009

front end guy
submissionscomments
grayrest··on Angular 2 versus React
> vanilla js running up to 40x faster than React

That particular link is a strawman argument. React isn't magically fast. Writing a 5 line shouldComponentUpdate would normalize the performance.

If you think you can do better with something else, feel free to ignore the React hype. For my part, I've been doing frontend full time more or less exclusively since 2005 and have tried dozens of libraries/frameworks in earnest. I don't know of a better way to write client side code.

grayrest··on Angular 2 versus React
There are a bunch of alternative implementations. The vdom benchmark [1] is a fairly good list but I've run across at least two more that aren't tracked there.

[1] http://vdom-benchmark.github.io/vdom-benchmark/

grayrest··on Angular 2 versus React
> React and Angular2 do the same thing yet they are both bloated and force tons of tools and tech down your throat. For what?

State control. The most important thing about a React app is that you can write your code to have every bit of application state inside a single variable. It gets you hot code loading, trivial testability, and session recording/replay. I can actually test the app without having to use selenium (which I hate). These aren't really benefits from React per se but from the vdom approach. My apps are in Clojurescript and don't use the majority of React's features so replacing it with an alternative vdom implementation is on my todo list for this year.

I haven't used it in anger, but Polymer is at the top of my list if I needed to write an app to integrate with a third party or something where I didn't have app state control.

grayrest··on Angular 2 versus React
> This was a few months ago, things may have improved.

Polymer improved substantially with the 1.0 release. It was basically a full rewrite so if you checked it out before then, you'll have to re-check.

grayrest··on Open Letter to Mozilla: Bring Back Persona
The message I got from Persona was "hey we're developing this thing, it'll be part of Firefox." Sounded like a pretty good idea to me and the signal for it being ready so I could reasonably push for adoption. Then it got shut down for no apparent reason.
grayrest··on RxJS is great. So why have I moved on?
> And there is not much to it, really.

The two use the same model but are organized differently so it throws me off a bit.

Re-frame chooses to hide the actual reduction step so instead of the big switch statement you have a bunch of registered event handlers, which are pure functions take the state and event and return the new state. The middleware is HoF around these so it's done per-handler as well as at the base reducer level, which turns out to be really good for code reuse.

> Is that the subscription mechanism? I have yet to find a use for it...

I think they call it selections. Reactions let you transform a normalized data model into a shape that's useful for the components. They also let you build up chains of reusable calculations/conditions to shift complex conditionals out of the components and into the model code so its co-located with the handlers, which are split up by the part of the app they work with (credentials, search, nav, etc).

grayrest··on RxJS is great. So why have I moved on?
Happy to hear that. I'm not actively using it and I find the the docs are fairly good but written from the implementor's perspective so they tend to bury the lede. I didn't think it had the equvalent of Reagent's reactions until I got towards the bottom of the react-redux page, which was the third or fourth time I looked at the docs. I'm not sure if I just missed it or it got added after I last looked.
grayrest··on RxJS is great. So why have I moved on?
I wrote a 20k LoC bacon app in 2014 and had the same issue with being able to understand all the pieces individually but having trouble comprehending the whole system.

I don't have non-architecture answers for how to organize code since that's pretty much the point of an architecture. Most of the application organization patterns around the React space are only ~200 lines of code and a description of how the pieces fit together. You don't have to adopt the whole thing immediately but you don't get the full benefits of a pattern until everything works the same way.

For my part, I got a job writing clojurescript full time and migrated the app to re-frame [1][2] this year in pieces over the course of a couple months and it's the first frontend architecture that's made me happy in over a decade of continuous searching. The js version is Redux but I think Redux is missing middleware as a concept.

[1] https://github.com/Day8/re-frame/ [2] https://github.com/binaryage/pure-frame <- Fork with no global atom

What this provides is a complete event cycle of completely pure functions (the state swap happens in the framework) with all the data in an immutable map in a single atom. By namespacing my subscriptions and events I can see a problem, look at the component, and know immediately where my problem is within ~10 lines of code. It's not perfect but it's pretty good.

One thing that doesn't get mentioned often enough when talking about Clojurescript is the potential for doing server-side rendering of the app without having to run node. [3]

[3] http://yogthos.net/posts/2015-11-24-Serverside-Reagent.html

grayrest··on Clojure at a Real Estate Portal
I'll answer these specifically for Clojure since they vary by language.

> encapsulation

Encapsulation is primarily a state control concept. It reduces the surface area of a piece of data to the object it's contained in. The reduction in the amount of code that can touch the data makes it easier to reason about. Clojure works around this by having most of its data be immutable and most functions pure. Debugging is walking up the stack trace to find the change you didn't expect.

> composition

Pure functions compose trivially. Encapsulating data in a class actually limits your composition by design. Using maps lets you re-use the many functions in the standard library pretty much everywhere.

> interfaces

Clojure has an extended version called protocols. It provides an api guarantee but it can be retroactively implemented on Java classes you don't control. Look up the expression problem in clojure for considerably more depth. You can also use multimethods if you want to dispatch on something besides class.

> Patterns

I subscribe to the argument that patterns are workarounds for limitations in a language. Most languages have them but nobody else formalizes them like the Java people. There are ways to accomplish the same objectives, generally with less ceremony.

> SOLID

Single Responsibilty - Concept carries over to functions in a namespace, a good idea in general

Open/Closed - I've always understood this to mean encapsulation, see above.

Liskov substitution - Dynamic languages are duck typed. If you want more assurance you can add something like Schema, which does structual subtyping instead of nominal subtyping.

Interface segregation - Preference in the community is for protocols to be VERY small, usually 1-5 functions.

Dependency inversion - Tends to not come up. Usually when you're designing an API you have a good idea for when a consumer is going to want different behavior. In those cases, you have the API take a function instead.

grayrest··on Cursive, an IDE for Clojure and ClojureScript, has reached 1.0
It's about the same but everybody uses vim a bit differently so YMMV. I switched from emacs+evil to cursive+ideavim. My annoyance with evil is that `xp` doesn't work, my annoyance with ideavim is that the `{` text object is scope based instead of text based. I've had Esc not work correctly under some circumstances in both (I use c-[ instead) and neither supports iskeyword so * doesn't search for identifier in skewer-case languages.
grayrest··on Cursive, an IDE for Clojure and ClojureScript, has reached 1.0
IdeaVIM provides reasonably good vim emulation. You'll probably get annoyed by something (I have yet to find a vim emulator I think is as good as Vim itself) but I only had to make a few minor changes in my vim patterns to get it working.
grayrest··on Rust 1.5
It'll get there simply because the language is doing pretty well and lots of people write web servers but it's not there yet. Once someone figures out "the way" to do an internet-facing async http/http2/websocket server I think we'll see a lot more motion in the Rust web dev space.
grayrest··on What Holds Me Back from ClojureScript
This is on a 2012 MBA for a prod build. I see times of 140s or 180s on a fresh dev build. Times are lower on faster machines but still in the multiple-minute range. I'll tweak the build JVM settings tomorrow and see if that improves things.
grayrest··on What Holds Me Back from ClojureScript
> non-lisp pretty-printer?

Brandon Bloom's fipp[1] works fine but the Chrome Canary dev tools have support for object formatters. I get reasonable results on .170 just doing a (js/console.log a-map).

[1] https://github.com/brandonbloom/fipp

grayrest··on What Holds Me Back from ClojureScript
I was roughly where you were at the beginning of the year. I've been in and out of the Clojure community for a couple years and decided to take a job writing clojurescript full time. App is around 15k loc and was initially in Om and has now been migrated to (my fork of) re-frame.

Positives: <3 re-frame. It's the first architecture in 15 years of frontend dev that I've been happy with and I've tried pretty much everything. cljs is a better language than es6. I like devcards.

Negatives: I've put two work weeks into dealing with tooling problems. I have a 5 minute build. This mostly doesn't matter because incremental builds but every so often (e.g. Tuesday) I hit a problem and have to bisect with a fresh build at each step.

While I'm happy with the language, I'm less happy than I expected to be. I'm in the process of porting devcards and re-frame patterns to (functional) Typescript for my personal projects. The language is a significant barrier to contribution from a larger community and I get a bigger lift from optional types+autocomplete than I do from protocols and macros.

Edit: Is anybody interested in a set of Sublime-ish keybindings for Cursive? I made the set for my coworkers and for the things I can't do via IdeaVim and they've been working fairly well for the past couple weeks.

grayrest··on To defend the free web, save Mozilla
> Didn't Mozilla just drop their contract with Google, saying they no longer needed that revenue?

There was a clickbait title that just came up but the contract came up months ago and Yahoo won the bid for the US default search provider.

grayrest··on Visual Studio Code is now open source
> pre Seamonkey naming

Maybe you predate me but when I jumped on the Mozilla bandwagon at Gecko 0.7.1 the internal name for the suite was Seamonkey. It was only after Firefox that the group maintaining the suite started using the internal name as the public branding.

grayrest··on Object.observe withdrawn from TC39
My assumption was that writing the handful of sCU I think you'd need is simpler than handling coordination. You seem to have a reasonable understanding of the tradeoffs.

> At that point, it seems you're not necessarily any better off than you would be with a more traditional, event-driven architectural style

Not having to implement the dom state updates and rAF update batching are fairly big wins. The only other way I know of doing this is via data binding and the difference there is that individual updates are more efficient but establishing the bindings is usually O(n_size_of_input) and in practice every app I've worked on that did data binding eventually broke the approach. Having the shouldComponentUpdate escape hatch has allowed me to push much larger volumes of data through the system without it breaking.

I've implemented a number of reasonably complex apps but the two largest have been on immutable datastructures where React's assumptions always hold unless you screw something up. The largest non-immutable app I've written was a 25k LoC layout builder where the two shouldComponentUpdates for vdom pruning were pretty straightforward.

Good luck with your app.

grayrest··on Object.observe withdrawn from TC39
Putting everything in one map and naively rendering it is a pattern that comes from the clojurescript subset of the React community and it works because the cljs wrappers assume cljs' immutable datastructures and all implement shouldComponentUpdate for you.

React isn't magically fast, it's just O(n_size_of_diff). Getting it to perform well here requires you to figure out how to prune the size of the diff. You're doing it by splitting the app state into finer grained trees (each tree is pruning the branches it's not listening to) but any gains you're picking up from doing this could be done on the single root solution with appropriate shouldComponentUpdate methods.

The alternative (and easier) way of reducing the diff size is to simply window your data before passing it into the render, which can work for things like grids and lists but doesn't sound like your problem.

You also need to occasionally reshape your data to make the diff smaller. I work on a search app that thrashed for 1.2s every time new search results came in. The problem was that the results were invalidating a recursive menu component which was re-rendering 4 times per update. Avoiding the invalidation fixed the specific delay but the menu was still slow. Switching the menu's data organization from at tree of objects with `open` attrs to a static tree + open path and only rendering the visible nodes solved the perf problems in the app.

grayrest··on Vue.js 1.0.0
I don't have a reference for this, but when it's come up in the past they've said size is not a primary goal for the project. A lot of the size comes from their internal event system that normalizes events cross-browser along with the extra abstraction for pluggable renderers (i.e. support React Native) and the component-local state handling. None of these are necessary to get the virutal dom model working. Finally, 132k minified is 40k after gzip. It's not THAT large but it's not a lightweight framework either.
grayrest··on Fastest JSON parser in the world is a D project?
My wishes are much more prosaic. It's not clear to me just from reading your docs how I can extract data from a JSON file using the pattern this benchmark shows (top level key containing the data, other keys containing metadata about the request) without having to create otherwise useless struct to cover the outer wrapper object.

I see you have a reply to Gankro for a non-exhaustive flag and that'd work. As for the default, the current behavior is what I'd expect from a Rust lib given the correctness first mindset of the community but I will always be opting for non-exhaustive because I think most people providing JSON APIs consider additional keys to be backwards compatible (they are in dynamic languages) and I'd prefer my apps to not break in production for no apparent reason.

grayrest··on A story about &lt;input&gt;
I always considered the quiet years up until 2005ish. After that we had XForms and the WhatWG hijincks to keep the web nerds entertained.
grayrest··on Clojure all the way down
I've heard people talk about it but I'm not aware of anybody actually trying it. I know that Rich has stated a number of times that he's not interested in working on Clojure-in-Clojure.
grayrest··on Fastest JSON parser in the world is a D project?
I enjoyed your blog series on serde perf.

One thing I noticed in this example was that the D example worked pretty much exactly like I want serde to work in that it was able to deserialize a subset of the overall document and the Coord struct didn't need to exhaustively cover the individual json data objects. If there's a way to do this in serde, an example in the docs would be really helpful.

grayrest··on Clojure all the way down
Clojurescript is the more "all the way down" of the two. It managed to self-host a few months ago so in theory you could write a backend and cross compile to your computing platform of choice. I know of a number of alternative targets but they all predate the self-hosting and none are actively maintained:

[1] https://github.com/takeoutweight/clojure-scheme [2] https://github.com/raph-amiard/clojurescript-lua

I think it'd be a fun project to get cljs-lua working and build a system for developing OpenResty apps but it's so low on my priority list I don't anticipate ever making it happen.

grayrest··on Fastest JSON parser in the world is a D project?
The JSON looks like this:

    {"coordinates": [
       {"x": 0.65, "y": 0.23, "z": 0.91, "name": "fwgzd", "opts": {'1': [1, true]},
       {"x": 0.45, "y": 0.78, "z": 0.22, "name": "alfsj", "opts": {'1': [1, true]},
       ...
    ],
     "info": "some info"}
The benchmark code [1] (very readable) is reading an array of structs containing x,y,z from 'coordinates'.

[1] https://github.com/kostya/benchmarks/blob/master/json/test_f...

I haven't read the code but the algorithm would look at this roughly like:

   See `{` process? yes
   See `"coordinates"` process? yes
   See `[` process? yes
   See `{` process? yes
   See `"x"` process? yes => new Coord, Coord.x = 0.65
   See `"y"` process? yes => Coord.y = 0.23
   See `"z"` process? yes => Coord.z = 0.91
   See `"name"` process? no, next token is `"`, skip to `"`
   See `"opts"` process? no, next token is `{`, skip to `}`
The tradeoff is that he's completely ignoring the contents of `name` or `opts` or `info` and those values could potentially be invalid JSON but this processor doesn't care.

The code is also picking up efficiencies from being a static C-like language. The "new Coord" isn't actually doing anything, the alloc happened for the array as a whole so the assignments just write a known size value to a known offset from the start of the array. He's also using SIMD instructions to process multiple bytes at a time and some other tricks but the skipping is the main difference.

I think it's also interesting that the Rust code implemented value skipping in the benchmark file itself. The relative slowness there is likely that the library used (serde_json) is the JSON plugin for a generic serialization/deserialization lib and that Rust doesn't have a way to do SIMD yet.

grayrest··on Fastest JSON parser in the world is a D project?
Here's the "how so fast" explanation:

https://github.com/kostya/benchmarks/pull/46#issuecomment-14...

grayrest··on Transit: JSON Data Interchange Format
The Clojurescript community are ahead but not by that much. To list specifics, cljs->babel+and immutable lib, figwheel->react-hot-loader, reagent->react 0.14 pure components, core.async->js-csp (or async/await).

In terms of non-component organization, I believe re-frame is a significant improvement over redux. Reactions are good when you don't control the endpoints, splitting out the reducers into pure functions is good, but adding middleware on them is the real win. On the other hand, it wouldn't be that hard to adapt the model to redux.

The next phase of organization is integrating Relay/Falcor concepts. David Nolan gave a talk about this in Om Next at NYC Clojure last month and there is a video. Om Next as presented is very compelling if you're on Datomic and less so otherwise.

As to your startup question:

I've been a full time cljs dev on a b2b app at Reuters for the past 9 months. I took up the job specifically because I wanted to write cljs. I had been involved with the Clojure community (I care about state) but only working on toy projects for the previous ~4 years.

My experience with Clojurescript is that it was less of an improvement on modern js than I was expecting. The biggest advantages are protocols and the standard library being both rich and standard. Nice to haves but non-critical are native syntax for immutable maps and multimethods. I guesstimate I write ~10% less code in cljs versus js but you're ultimately writing the same stuff.

Problems I've run into:

Full build time for this app is long. Our app is in the 15k LoC range across ~150 files and cold compile is 140s on a 2012 MBA. It's annoying but incremental compilation times are sub-second after some build config tweaking.

We have one component in particular that tends to get lost when switching branches and the missing namespace forces a fresh compile. Our cljs version is from June so this may be fixed. I've also spent a number of hours debugging problems that turned out to be stale build issues.

I tried a couple times unsuccessfully to get Emacs (cider) to connect to a figwheel repl. After a few evals things simply become unresponsive. Just using figwheel is good enough but I miss the in-editor repl. Haven't tracked down the reason, could be my lack of emacs knowledge.

If you're using core.async, the main loop has a try/catch/rethrow. This causes Chrome Dev Tools to break in the outer loop instead of actually at the problem. You have to explicitly err.stack in the console (which is not source mapped) and don't have access to the locals unless you manually set a breakpoint at the error and reload. You also get to learn to read the JS representation of Clojure literals. None of this is impossible and if you're working in a tight loop you tend to have a pretty good idea of what the error is without jumping through debugger hoops but if you're doing something like switching branches or refactoring it's annoying.

I like Reagent but I've had a number of times where its behavior doesn't match my expectations. In particular, figuring out what part of the vdom is invalidated on a ratom change caused me problems. There's a gotcha that sequences must be forced with doall or you'll get weird behavior. At the moment I have a very expensive reaction (list processing ~6k items) that's getting run 8 times in response to a single key change in the source ratom so I'll be tracking that down tomorrow.

I don't consider this list a reason to not adopt Clojurescript. I can make a similar list for the Babel stack.

As for the question of would I be comfortable, I like writing clojurescript but I'd only really recommend it if you're committed to full stack Clojure. It takes a number of weeks for a new frontend hire to ramp up on the language. I've discussed this with the other frontend specialist on the team and our consensus is that cljs is a better language but we're not that much more productive in the language compared to ES2015 so I'm not really convinced the weeks of ramp up time are worth it. Our experience with hiring has been that we've had very few candidates but they've all been skilled.

grayrest··on React v0.14
The Elm and Reagent (Clojurescript) communities build apps this way. It's theoretically possible to do everything in render in practice you wind up wanting to do things in other lifecycle events (e.g. display a google map) in most apps. There isn't any component local state but the components aren't pure render functions either.
grayrest··on Rust Faster
> if rust could compile within 5-10x the time go does, it would be much more pleasant to work with

As someone who's been around Rust for a long time but writes very little code in the language, I'm looking towards the MIR work and language services effort to improve this. The language is reasonable to work with once you're over the learning curve and you can mentally visualize the allocations/pointers/borrows moving around but the learning curve is really hard. Having a faster feedback loop on a higher information density interface than console text (I have a dream of dynamic borrow/lifetime overlays) to act as an extended tutorial would go a very long way.

← PreviousPage 8 of 23Next →