3 minutes read ·
2021-04-13
It’s hard to overstate how successful HTML and CSS really are. Having evolved dramatically since their humble origins in the 90s to allow for a wide range of interoperable, accessible and aesthetically rich documents and experiences, these two technologies are now the bread-and-butter that underpins the critical path of a double digit portion1 of the world’s economy.
With that said, it’s only natural that after nearly 30 years of standards development some non-optimal decisions have been made, particularly when it comes to default behaviors.
Lately I have been playing AI Dungeon, a web-based text game that’s set up very much like a classic text adventure: It gives you an initial story blurb, and you type in commands (e.g., walk north or pick up axe) to explore the world and advance.
The key twist is that there’s no hardcoded implementation of how the game world works. The commands are fed into an AI model that simply “autocompletes” a plausible storyline. Beyond some input trickery to make sure the AI remembers the “context” around the setting and your character, it’s all free-form text.
While sometimes it prints out the odd or nonsensical result that one would normally associate with AI-generated content, I found that it works scarily well. The high-end AI model (“Dragon”) uses GPT-3 behind the scenes and is really, really good at producing good, engaging storylines.
Since GPT-3 is trained from a huge dataset of internet content, I thought it would be interesting to “interview” it inside the game world to see what it had to say on a variety of topics. I started a “Custom” game, and to nudge it in the direction of giving me high quality, intellectually meaningful results (instead of just channeling every single Internet troll in existence) I started with the following prompt:
You are sitting in front of the great oracle that can answer any question. Their eyes stare through you, fixated into the distance. This being has existed for eons. They look at you and say “what is your question?”
On August 10 Facebook released version 17.0.0-rc0 of the React JavaScript framework. This is a pretty unusual React release in that it has fundamentally no new features. Instead, it contains a very small set of breaking changes designed to facilitate the migration to future versions of the library.
3 years of React 16
Given that the JavaScript ecosystem typically moves at breakneck pace, it’s almost impossible to believe that the 16.x line of React releases has now been going on for almost three years, with full backwards compatibility:
16.0 - Error Boundaries, Portals
16.2 - React.Fragment
16.3 - New Context API
16.6 - React.memo(), React.lazy()
16.8 - Hooks!
16.9 - Lifecycle Deprecations
From 16.0 to 16.13.1, the experience of using React was completely transformed: A lot of features that were previously experimental or unergonomic to use got first-class support (Like portals and context) and we went through a lot of trials in composability, by means of Higher Order Components, Render Props and ultimately Hooks in 16.8.
What was once a Class-based object-oriented UI framework is now a Pure-functional, algebraic effects-inspired scheduler for arbitrary computation, that yeah, is still pretty good for building UIs.
And all of that without breaking backwards compatibility: Assuming you were not using private or experimental APIs (and were not depending on a library that did it) the same project written for React 16.0 should still run under React 16.13.1, with maybe a couple warnings.
Preserving that compatibility is one of the biggest accomplishments (and selling points) of React as a framework, and it makes sense: Facebook has a massive codebase with thousands of components and pages that it needs to maintain, so API stability is a must.
All of that comes at a cost, though. Some of the highly anticipated features (like Concurrent mode and Suspense for data fetching) ended up taking way more work than expected, and will require breaking backwards compatibility. React 16.9 already deprecated a bunch of lifecycle methods and saw them marked as UNSAFE_ for concurrent mode. It looks like the React team has decided it’s finally time to move on, and start shedding support for those deprecated methods for good.
Before they can do that though, they’re introducing an escape hatch for legacy codebases that can’t upgrade, in the form of React 17, by officially supporting a workflow where multiple versions of the React framework can more easily coexist in the same page side by side, and even nested “inside” each other. Unfortunately, getting that to work properly requires some breaking changes, albeit very small ones.
Roughly a month has passed since I started using the Rust programming language as my “daily driver”, and I feel like it would be interesting to share my early impressions and thoughts about the language.
NB: The title of this post is not entirely accurate, since I had been toying on and off with the language in my free time for a while. Rust has a steep learning curve, so don't feel demotivated if you don't hit the ground running in the first month.
Rust?
Rust is a systems programming language with a focus on execution speed, memory and data-race safety. It's sponsored primarily by Mozilla, who are using it to develop their next generation browser engine, Servo.
Learning Curve
My first impression of Rust was that it was significantly harder to learn than most languages I had experimented with, perhaps with the exception of Haskell. During the first weeks, it felt like I didn't know how to properly structure anything beyond the simplest programs. That was quite frustrating*.
My advice if you're struggling in this stage is to power through and keep trying to use the language, even if you can't fully understand what's going on yet or get everything to work. Much like learning git and vim, it does pay off later on. After some time, it's finally going to “click”, and you'll start to develop an intuition of how things work.
The following were my major pain points during my first month with rust:
Pain Point #1: Move Semantics
Most other languages provide either copy semantics or reference semantics for dealing with values. What that means is: if you pass a variable x into a function f(), it will receive either a copy of the value in x or a reference to it. (Depending on the language and the type of x)
Rust does away with that, and opts to use move semantics† by default for most custom data types‡. Attempting to compile the following code:
fn f(x: String) {
// Do something with x
}
fn main() {
let x = String::from("Hello");
f(x);
println!("{}", x);
}
Run
Will result in an error, since the value of x is consumed by f(), and cannot be reused by println!():
rustc 1.16.0 (30cf806ef 2017-03-10)
error[E0382]: use of moved value: `x`
--> <anon>:8:20
|
7 | f(x);
| - value moved here
8 | println!("{}", x);
| ^ value used here after move
|
= note: move occurs because `x` has type `std::string::String`, which does not implement the `Copy` trait
error: aborting due to previous error
Instead, you need to either explicitly clone the value of x, or take a reference and pass that instead:
fn f(x: String) {
// Do something with x
}
fn g(x: &String) {
// Do something with x
}
fn main() {
let x = String::from("Hello");
f(x.clone());
g(&x);
println!("{}", x);
}
Run
This forces you to think about ownership, which is very convenient for providing automatic memory management and compile-time optimizations. However, it does take some time getting used to.
* It doesn't help that most of the “classic” CS 101 assignments that people will commonly implement when learning a new language—e.g. linked lists, binary trees, hash tables—all traditionally involve shared mutable state, which is heavily discouraged by the language in the first place.↩ † Rust was not the first programming language to introduce move semantics for values. C++11 implements it to some extent via move constructors. ↩ ‡ Primitive data types—and in fact all types that implement the Copy trait—are exempt from this rule, and are instead passed by copy. ↩