{"version":"https://jsonfeed.org/version/1.1","title":"Tomer Aberbach","home_page_url":"https://tomeraberba.ch","feed_url":"https://tomeraberba.ch/feed.json","description":"The portfolio website and blog of Tomer Aberbach, a New Jersey based software engineer, composer, and music producer.","authors":[{"name":"Tomer Aberbach","url":"https://tomeraberba.ch","avatar":"https://tomeraberba.ch/build/_assets/avatar-U5KWRIRC.png"}],"language":"en-US","items":[{"id":"when-two-bugs-cancel-out","url":"https://tomeraberba.ch/when-two-bugs-cancel-out","title":"When Two Bugs Cancel Out","content_html":"
I encountered a perplexing bug while implementing table cell splitting in Google Docs.
The QA team found a bug where some of a tableâs borders would be missing after a table cell split in certain scenarios. However, the borders reappeared when refreshing the page. That meant the documentâs data was likely stored correctly and that incremental layout or rendering had a bug.
At the time, the team was migrating Google Docs from HTML-based rendering to canvas-based rendering. QA reported the bug about a canvas-rendered document, which made me suspect it was a bug in canvas-based rendering. So I tried to reproduce the issue on an HTML-rendered document and it didnât reproduce!
At this point I concluded the new canvas-based rendering must have a bug. Oh how wrong I was⌠After much investigation, I realized canvas-based rendering was correct.
In Google Docs, the output of layout is the input for rendering. It turned out layout had a bug and canvas-based rendering was correctly displaying layoutâs incorrect output. So why did HTML-based rendering look correct? It turned out that HTML-based rendering also had a bug, and the bug coincidentally âfixedâ layoutâs output so that the final output looked correct.
So how did the HTML-based rendering bug cancel out the layout bug? One of the reasons the team switched to canvas-based rendering, other than better performance, is that itâs more precise than HTML-based rendering.
With canvas-based rendering you effectively have to specify the location of every pixel, which means it can accurately render any layout. It did just that for the incorrect layout output!
On the other hand, with HTML-based rendering a table is rendered using a table
element, which supports a limited set of layouts. HTML-based rendering could not faithfully render the incorrect layout output so it rendered the closest thing, which coincidentally looked correct.
Have you ever wanted to use tuples or objects for the keys of a Map
or the values of a Set
? Itâs a very common question because the following code doesnât do what you might expect:
const map = new Map()\nmap.set([1, 2], 3)\nconsole.log(map.get([1, 2]))\n//=> undefined\n\nconst set = new Set()\nset.add([1, 2])\nconsole.log(set.has([1, 2]))\n//=> false
The code behaves this way because a Map
âs keys and a Set
âs values are compared using reference equality, as specified by the SameValueZero algorithm. Two deeply equal arrays are not referentially equal:
console.log([1, 2] === [1, 2])\n//=> false
The following code behaves more predictably:
// A reference to a single array instance!\nconst tuple = [1, 2]\n\nconst map = new Map()\nmap.set(tuple, 3)\nconsole.log(map.get(tuple))\n//=> 3\n\nconst set = new Set()\nset.add(tuple)\nconsole.log(set.has(tuple))\n//=> true
But that isnât particularly useful because typically youâre constructing tuples on the fly using values from some external source:
const f = (x, y) => {\n // This will return undefined because `[x, y]` is a new array!\n const value = map.get([x, y])\n\n // ...\n}
A common solution is to use JSON.stringify
:
const map = new Map()\nmap.set(JSON.stringify([1, 2]), 3)\nconsole.log(map.get(JSON.stringify([1, 2])))\n//=> 3\n\nconst set = new Set()\nset.add(JSON.stringify([1, 2]))\nconsole.log(set.has(JSON.stringify([1, 2])))\n//=> true
This works because strings are primitives, which are compared by value rather than by reference. Unfortunately, using JSON.stringify
requires that the inner values are stringifiable. If theyâre not, then youâre forced to write a custom serialization function.
Plus, sometimes you do want reference equality, per inner value, but serialization doesnât preserve reference equality:
const person1 = { name: `Tomer`, occupation: `Software Engineer` }\nconst person2 = { name: `Tomer`, occupation: `Software Engineer` }\n\nconst salaryApplications = new Map()\nsalaryApplications.set(JSON.stringify([person1, Number.MAX_VALUE]), `approved`)\n\n// Oh no! Two different software engineers named Tomer are considered the same due to stringifying!\nconsole.log(salaryApplications.get(JSON.stringify([person2, Number.MAX_VALUE])))\n//=> approved
Surely thereâs a better way!
keyalesce
keyalesce
is a module that returns the same unique key for the same value sequence1. Itâs perfect for this use case:
const person1 = { name: `Tomer`, occupation: `Software Engineer` }\nconst person2 = { name: `Tomer`, occupation: `Software Engineer` }\n\nconst map = new Map()\nmap.set(keyalesce([1, 2]), 3)\nmap.set(keyalesce([2, `b`, 3]), 4)\nmap.set(keyalesce([person1, Number.MAX_VALUE]), `approved`)\nmap.set(keyalesce([person2, Number.MAX_VALUE]), `totally approved`)\n\nconsole.log(map.get(keyalesce([1, 2])))\n//=> 3\n\nconsole.log(map.get(keyalesce([2, `b`, 3])))\n//=> 4\n\nconsole.log(map.get(keyalesce([person1, Number.MAX_VALUE])))\n//=> approved\n\nconsole.log(map.get(keyalesce([person2, Number.MAX_VALUE])))\n//=> totally approved\n\nconst set = new Set()\nset.add(keyalesce([1, 2, 3, 4, 5]))\nset.add(keyalesce([3, 3, 2, 2, 1]))\n\nconsole.log(set.has(keyalesce([1, 2, 3, 4, 5])))\n//=> true\n\nconsole.log(set.has(keyalesce([3, 3, 2, 2, 1])))\n//=> true\n\nconsole.log(keyalesce([1, 2, 3, 4, 5]) === keyalesce([1, 2, 3, 4, 5]))\n//=> true
keyalesce
internally maintains a trie containing the sequences of values it has been called with. It creates and returns new keys for new sequences and returns previously created keys for known sequences!
For example, the following code:
const key1 = keyalesce([1, 2, 3, 4])\nconst key2 = keyalesce([1, 2, 3])\nconst key3 = keyalesce([1, 2, 7, 8])\nconst key4 = keyalesce([`a`, `b`, `c`])
Would result in the following trie:
And calling keyalesce([1, 2, 3, 4])
again would return key1
after traversing nodes 1
, 2
, 3
, and 4
in the trie.
keyalesce
cause memory leaks?A long running program using a naive implementation of keyalesce
would have a memory leak due to unbounded growth of the trie. How are unreachable nodes pruned?
Consider the following code:
const object1 = {}\nconst object2 = {}\n\nconst key1 = keyalesce([1, object1, 4])\nconst key2 = keyalesce([1, 2, object2, 3])\n\n// ...
Which would result in the following trie:
If the code continues like so:
object2 = null
Then object2
âs original value is only reachable from the trie. keyalesce
can now prune the associated sequence and its key from the trie because it has become impossible for keyalesce
to be called with that sequence ever again.
I made the trie hold only weak references to objects passed to keyalesce
and pruned the trie when the objects are garbage-collected using FinalizationRegistry
.
After pruning in this case the trie would look like so:
Consider the following code:
const key1 = keyalesce([1, 2, 4])\nconst key2 = keyalesce([1, 2, 5, 7])\n\n// ...
Which would result in the following trie:
The previous sectionâs logic does not apply because all of the sequence values are primitives, which are always reachable and not eligible for garbage collection. So how is the trie pruned in this case?
If the code continues like so:
key2 = null
Then key2
âs original value is only reachable from the trie. Although itâs still possible to call keyalesce
with [1, 2, 5, 7]
, keyalesce
can actually prune the key and its associated value sequence because there isnât any code that depends on receiving that specific key anymore. keyalesce
doesnât need to return the same unique key for a given value sequence. It only needs to prevent multiple keys existing simultaneously for the same value sequence.
Similarly to the handling of object sequence values, I made the trie hold only weak references to created keys and pruned the trie when the keys are garbage-collected using FinalizationRegistry
.
After pruning in this case the trie would look like so:
In summary, the trie is pruned whenever object sequence values or keys have only weak references to them.
Install keyalesce
:
$ npm i keyalesce
And import it:
import keyalesce from 'keyalesce'\n\nconst hangouts = new Set()\n\nconst createHangoutKey = (person1, person2) =>\n keyalesce([person1, person2].sort())\nconst hangOut = (person1, person2) =>\n hangouts.add(createHangoutKey(person1, person2))\nconst didTheyHangOut = (person1, person2) =>\n hangouts.has(createHangoutKey(person1, person2))\n\nhangOut(`Tomer`, `Sam`)\nhangOut(`Tomer`, `Amanda`)\n\nconsole.log(didTheyHangOut(`Tomer`, `Sam`))\nconsole.log(didTheyHangOut(`Sam`, `Tomer`))\n//=> true\n//=> true\n\nconsole.log(didTheyHangOut(`Tomer`, `Amanda`))\nconsole.log(didTheyHangOut(`Amanda`, `Tomer`))\n//=> true\n//=> true\n\nconsole.log(didTheyHangOut(`Sam`, `Amanda`))\nconsole.log(didTheyHangOut(`Amanda`, `Sam`))\n//=> false\n//=> false
Two value sequences are considered equal if each of their values are equal using the SameValueZero algorithm. Back to content
When your CSS references a web font before it finishes downloading, the browser renders text using a fallback system font instead, causing a layout shift if the text containerâs height changes once itâs rendered with the web font.
Optimizing your font files and using an intelligent font loading strategy can eliminate layout shifts, but only if the web font is not immediately used on the page. Otherwise you have to ensure the text containerâs height doesnât change. Fontpie can help with this. Itâs a tool for generating CSS that adjusts a fallback fontâs metrics so it takes up the same amount of space as your web font. The result is no layout shifts even if the font is immediately used!
Part of what makes Fontpie great is that itâs framework agnostic, but that also means itâs a bit manual to use and I wanted something I could easily integrate into this websiteâs build. I was already using PostCSS so I decided to make a Fontpie PostCSS plugin. The result is postcss-fontpie
. And now generating fallback font metrics for my web fonts is as easy as adding the following to my PostCSS config:
const { join } = require(`path`)\n\nmodule.exports = {\n plugins: [\n // ...\n require(`postcss-fontpie`)({\n fontTypes: {\n dm: `mono`,\n 'Kantumruy Pro': `sans-serif`,\n },\n srcUrlToFilename: url => join(__dirname, `src/styles`, url),\n }),\n // ...\n ],\n}
You can see the generated fallback font metrics in this websiteâs CSS and their effects in the following before-and-after GIFs:
Check out postcss-fontpie
âs usage example to use it in your own website!
Naturally this list only includes projects Iâm allowed to talk about! For many projects I was not the only person who worked on it. The projects are ordered from most to least recent.
I worked on integrating generative AI with Docs!
Many news outlets took note:
I worked on code blocks for Docs, including the Markdown support. The Verge took note:Â âGoogleâs making code formatting a breeze in Docsâ.
I made it possible to split table cells in Docs.
I implemented edit notifications for Docs. While working on the feature, I also added a UI for updating a documentâs comment and edit notification settings directly from the emails using dynamic mail.
I added Markdown autocorrection/substitution to Docs, Slides, and Drawings1.
Several news outlets took note:
I added en- and em-dash autocorrection/substitution to Docs, Slides, and Drawings.
I increased the width of comment threads in Docs. They were way too narrow and it was bothering me! đ
I made Drive sharing emails show the most recent file thumbnail, owner, and edit when opening the emails, and added a UI for starring and unstarring the files directly from the emails, all using dynamic mail.
I added link previews for links in Docs, Sheets, Slides, and Drawings comment threads. Previously, they only appeared for links in the editor itself.
I made it easier to find new or important comment threads in Docs. USA TODAY took note:Â âGoogle Docs upgrades comments feature to make collaboration easierâ.
In the Summer of 2019 I interned on the Sites team and allowed site viewers to provide feedback on published Sites.
Yes, Google has an editor called Drawings. Back to content
I composed, produced, and released an instrumental piano track!
You can also find the track on Spotify, Apple Music, YouTube Music, and other music streaming services. Special thanks to Emily Kazenmayer for the album cover!
","image":"https://tomeraberba.ch/complexity.png","date_published":"2023-01-14T00:00:00.000Z","tags":["piano","track"]},{"id":"the-hero-archetype","url":"https://tomeraberba.ch/the-hero-archetype","title":"The Hero Archetype","content_html":"I composed, produced, and released an instrumental piano track!
You can also find the track on Spotify, Apple Music, YouTube Music, and other music streaming services. Special thanks to Jill Marbach for the album cover!
","image":"https://tomeraberba.ch/the-hero-archetype.png","date_published":"2022-09-02T00:00:00.000Z","tags":["piano","track"]},{"id":"checking-for-the-absence-of-a-value-in-javascript","url":"https://tomeraberba.ch/checking-for-the-absence-of-a-value-in-javascript","title":"Checking for the Absence of a Value in JavaScript","content_html":"JavaScript has a lot of similar-looking ways to check for the absence of a value:
console.log(value == null)\nconsole.log(value === null)\nconsole.log(value == undefined)\nconsole.log(value === undefined)\nconsole.log(value === undefined || value === null)\nconsole.log(typeof value === 'undefined')\nconsole.log(typeof value == 'undefined')\nconsole.log(typeof value === 'undefined' || value === null)\nconsole.log(typeof value === 'undefined' || value == null)\nconsole.log(typeof value == 'undefined' || value == null)\nconsole.log(typeof value == 'undefined' || value === null)\nconsole.log(!value)
Which one is right?
JavaScript has two representations of an absent value.
undefined
is a JavaScript primitive type. The typeof
operator returns 'undefined'
for undefined
:
console.log(typeof undefined)\n//=> undefined
The value of a declared unassigned variable is undefined
:
let x\nconsole.log(x)\n//=> undefined
The access of an absent object property returns undefined
:
const object = {}\nconsole.log(object.absentProperty)\n//=> undefined
The return value of a function that doesnât explicitly return is undefined
:
function f() {}\nconsole.log(f())\n//=> undefined
The void
operator always returns undefined
1:
console.log(void 0)\n//=> undefined\n\nconsole.log(void 'hello')\n//=> undefined\n\nconsole.log(void (3 + 2))\n//=> undefined\n\nconsole.log(void (/* any expression */))\n//=> undefined
Lastly, undefined
is not a literal! It is a property of the global object, which always exists in the global scope.
null
is also a JavaScript primitive type, but typeof
returns something unexpected for null
:
console.log(typeof null)\n//=> object
Ideally typeof null
would return 'null'
, but typeof null
has returned 'object'
since JavaScriptâs inception and it would break existing code if the behavior were changed now.
null
does not appear as a âdefaultâ value in JavaScript in the same way that undefined
does. Instead, developers typically make functions return null
when an object canât be found or constructed. For example, in browsers document.getElementById
returns null
if thereâs no element in the document with the given ID:
console.log(document.getElementById('some-id-that-no-element-has'))\n//=> null
Unlike undefined
, null
is a literal. It is not a property of the global object.
Now that weâve covered undefined
and null
, letâs address the difference between ==
and ===
.
Strict equality is invoked using ===
. For two values, a
and b
, a === b
evaluates to true
if a
and b
have the same type and their values are equal. Otherwise, a === b
evaluates to false
:
console.log(0 === 0)\n//=> true\n\nconsole.log('hello!' === 'hello!')\n//=> true\n\nconsole.log(null === null)\n//=> true\n\nconsole.log(undefined === undefined)\n//=> true\n\nconsole.log(0 === 5)\n//=> false (same types, but different values)\n\nconsole.log(0 === '0')\n//=> false (different types)\n\nconsole.log(0 === 'hello!')\n//=> false (different types)\n\nconsole.log(null === undefined)\n//=> false (different types)\n\nconst object = {}\n\nconsole.log(object === {})\n//=> false (because objects are compared by reference)\n\nconsole.log(object === object)\n//=> true (because the object is referentially equal to itself)
Loose quality is invoked using ==
and often produces unexpected results. For two values of the same type, a
and b
, a == b
behaves like a === b
. If a
and b
have different types, then JavaScript coerces the values to the same type and strictly equates them:
console.log(1 == 1)\n//=> true\n\nconsole.log(1 == '1')\n//=> true (because the string was converted to a number)\n\nconsole.log('1' == 1)\n//=> true (because the string was converted to a number)\n\nconsole.log(0 == false)\n//=> true (because the boolean was converted to a number)\n\nconsole.log(0 == null)\n//=> false (because absent values are not considered equal to non-absent values)\n\nconsole.log({} == {})\n//=> false (because objects are compared by reference)\n\nconsole.log(0 == undefined)\n//=> false (because absent values are not considered equal to non-absent values)\n\nconsole.log(null == undefined)\n//=> true (because both are absent values)\n\nconsole.log(undefined == null)\n//=> true (because both are absent values)\n\nconsole.log('hello!' == false)\n//=> false\n\nconsole.log('' == false)\n//=> true (because the string was converted to a boolean and an empty string sort of represents false in the realm of strings I guess)\n\nconsole.log([] == false)\n// true (because the array was converted to a boolean and an empty array sort of represents false in the realm of arrays I guess)
If youâre feeling confused, then you wouldnât be the only one. This operand conversion table and article about âtruthyâ and âfalsyâ values explain loose equality more fully. For a handy reference on the behavior of ==
and ===
, look no further than this JavaScript equality table.
Now we can check which expressions from the beginning of the post work! Letâs take a look at the first expression:
console.log(value == null)
Does it evaluate to true
for undefined
? Yes, because undefined
and null
are loosely equal.
Does it evaluate to true
for null
? Yes, because null
is equal to itself.
Does it evaluate to false
for everything else? Yes, because null
is only loosely equal to itself and undefined
.
value == undefined
would also work for roughly the same reasons, but value == null
is safer because undefined
could be shadowed or reassigned in pre-ES5 JavaScript environments. This canât happen with null
because it is a literal.
These methods work except for one lurking issue. If value
is undeclared, then our code would throw a ReferenceError
.
That may sound like a nonissue, but consider that some JavaScript needs to be compatible with both the browser and Node.js, and that the two environments differ in which global variables are declared. For example, in the browser the global variable window
is declared, but thereâs no such variable in Node.js. Can we access the window
variable only if it exists and avoid a ReferenceError
?
It turns out that the typeof
operator returns 'undefined'
for an undeclared variable instead of throwing a ReferenceError
. This is convenient because typeof undefined
also returns 'undefined'
so typeof value === 'undefined'
checks for both undeclared variables and undefined
. To check for null
as well we can add an additional check using logical âorâ.
console.log(typeof value === 'undefined' || value === null)
Does it evaluate to true
when value
is undeclared? Yes, because the typeof
operator returns 'undefined'
for an undeclared variable.
Does it evaluate to true
for undefined
? Yes, because typeof undefined
returns 'undefined'
.
Does it evaluate to true
for null
? Yes, the first condition evaluates to false
, but the second condition evaluates to true
because null
is equal to itself.
Does it evaluate to false
for everything else? Yes, the typeof
operator only returns 'undefined'
for undeclared variables and undefined
, and null
is only strictly equal to itself.
This method works in every situation, but it is only preferable over value == null
when you donât know if value
has been declared2.
A few of the expressions at the beginning of the post look almost identical to the expression we just evaluated. In fact, the following expressions are equivalent to typeof value === 'undefined' || value === null
:
console.log(typeof value === 'undefined' || value === null)\nconsole.log(typeof value === 'undefined' || value == null)\nconsole.log(typeof value == 'undefined' || value == null)\nconsole.log(typeof value == 'undefined' || value === null)
So why choose the expression that uses strict equality for both conditions? I prefer to avoid loose equality because itâs confusing and in this case itâs not required for correct behavior.
Letâs evaluate the rest of the expressions from the beginning of the post:
// Doesn't account for undefined\nconsole.log(value === null)\n\n// Doesn't account for null\nconsole.log(value === undefined)\n\n// Works, but is much more verbose than value == null\nconsole.log(value === undefined || value === null)\n\n// Doesn't account for null\nconsole.log(typeof value === 'undefined')\n\n// Doesn't account for null\nconsole.log(typeof value == 'undefined')\n\n// Erroneously evaluates to true for falsy values such as false, '', [], and 0\nconsole.log(!value)
An object property can be set to an absent value, but the property itself can also be absent:
const object1 = {}\nconst object2 = { property: undefined }\n\nconsole.log(object1.property == null)\n//=> true\n\nconsole.log(object2.property == null)\n//=> true
The result for the two objects is the same because the access of an absent property returns undefined
. This makes value == null
a good solution when checking for null
, undefined
, and absent properties. However, specifically checking for an absent property requires a different method.
One way is to use the in
operator:
const object1 = {}\nconst object2 = { property: undefined }\n\nconsole.log('property' in object1)\n//=> false\n\nconsole.log('property' in object2)\n//=> true
Note that the left-hand side of the in
operator must be a string
or Symbol
, not an identifier. This may seem like a good solution, but consider the following case:
const object1 = {}\nconst object2 = { constructor: undefined }\n\nconsole.log('constructor' in object1)\n//=> true\n\nconsole.log('constructor' in object2)\n//=> true
Probably not what you expected right? The expression 'constructor' in object1
returns true
because the constructor
property was inherited from the objectâs prototype chain. The in
operator considers both the specific properties of the object as well as its inherited properties.
This a nonissue when the object has a null
prototype because there are no inherited properties:
const object = Object.create(null)\nconsole.log(`constructor` in object)\n//=> false
But most of the time the object doesnât have a null
prototype or we donât know if it does. A more robust solution is to only check the uninherited properties using the hasOwnProperty
method, which is inherited from Object
:
const object1 = {}\nconst object2 = { constructor: undefined }\n\nconsole.log(object1.hasOwnProperty('constructor'))\n//=> false\n\nconsole.log(object2.hasOwnProperty('constructor'))\n//=> true
There are a couple of pitfalls to using the hasOwnProperty
method:
const object1 = { hasOwnProperty: () => true }\nconst object2 = Object.create(null)\n\nconsole.log(object1.hasOwnProperty('property'))\n//=> true\n\nconsole.log(object2.hasOwnProperty('property'))\n//=> TypeError: object2.hasOwnProperty is not a function
object1
âs hasOwnProperty
method was shadowed by a method that always returns true
. object2
was created with a null
prototype so it does not inherit hasOwnProperty
from Object
. There are two ways around these pitfalls:
Access Object
âs hasOwnProperty
method directly:
const object = { hasOwnProperty: () => true }\nconsole.log(Object.prototype.hasOwnProperty.call(object, 'property'))\n//=> false
Use Object
âs static hasOwn
method:
const object = { hasOwnProperty: () => true }\nconsole.log(Object.hasOwn(object, 'property'))\n//=> false
hasOwn
was added to JavaScript to avoid hasOwnProperty
âs pitfalls, but at the time of writing it is relatively new.
Checking if value
is set to an absent value:
value == null
Checking if value
is undeclared or set to an absent value:
typeof value === 'undefined' || value === null
Checking if 'property'
in object
is absent or set to an absent value:
object.property == null
Checking if property
in object
is absent:
!Object.prototype.hasOwnProperty.call(object, 'property')
Checking if property
in object
is absent in modern browsers:
!Object.hasOwn(object, 'property')
MDN has some examples of when the void
operator is useful. Back to content
CoffeeScript follows the same principle when transpiling its existential operator to JavaScript. Back to content