{"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.

\"TheThe bug report 🐛

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.

\"TheThe investigation 🕵️

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.

\"TwoTwo bugs 🐛🪲

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.

layout (bug)
HTML-based rendering (bug)
canvas-based rendering
coincidentally correct output
incorrect output

\"How?How? 🤔

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.

","image":"https://tomeraberba.ch/when-two-bugs-cancel-out.png","date_published":"2023-09-18T00:00:00.000Z","tags":["bug","code","docs","google"]},{"id":"the-making-of-keyalesce","url":"https://tomeraberba.ch/the-making-of-keyalesce","title":"The Making of Keyalesce","content_html":"

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!

\"TheThe solution: 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

\"HowHow does it work?

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:

1
2
3
4
7
8
root
key1
key2
key3
key4
a
b
c

And calling keyalesce([1, 2, 3, 4]) again would return key1 after traversing nodes 1, 2, 3, and 4 in the trie.

\"DoesDoes 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?

\"SequenceSequence value reachability

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:

1
2
3
4
root
key1
key2
object1
object2

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:

1
4
root
key 1
object1

\"CreatedCreated key reachability

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:

1
2
4
5
7
root
key1
key2

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:

1
2
4
root
key1

In summary, the trie is pruned whenever object sequence values or keys have only weak references to them.

\"GoGo use it!

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

Footnotes

  1. Two value sequences are considered equal if each of their values are equal using the SameValueZero algorithm. \"\"Back to content

","image":"https://tomeraberba.ch/the-making-of-keyalesce.png","date_published":"2023-07-02T00:00:00.000Z","tags":["code","data structures","gc","javascript","performance","tries"]},{"id":"avoid-layout-shifts-caused-by-web-fonts-with-postcss-fontpie","url":"https://tomeraberba.ch/avoid-layout-shifts-caused-by-web-fonts-with-postcss-fontpie","title":"Avoid Layout Shifts Caused by Web Fonts With PostCSS Fontpie","content_html":"

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:

postcss.config.js
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!

","image":"https://tomeraberba.ch/avoid-layout-shifts-caused-by-web-fonts-with-postcss-fontpie.png","date_published":"2023-05-24T00:00:00.000Z","tags":["code","fonts","javascript","performance","postcss"]},{"id":"what-ive-worked-on-at-google","url":"https://tomeraberba.ch/what-ive-worked-on-at-google","title":"What I’ve Worked On at Google","content_html":"

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.

Footnotes

  1. Yes, Google has an editor called Drawings. \"\"Back to content

","image":"https://tomeraberba.ch/what-ive-worked-on-at-google.png","date_published":"2023-04-23T00:00:00.000Z","tags":["code","docs","drawings","google","resume","sheets","sites","slides"]},{"id":"complexity","url":"https://tomeraberba.ch/complexity","title":"Complexity","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 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?

\"AbsentAbsent values

JavaScript has two representations of an absent value.

\"UndefinedUndefined

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 undefined1:

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.

\"NullNull

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.

\"EqualityEquality

Now that we’ve covered undefined and null, let’s address the difference between == and ===.

\"StrictStrict

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)

\"LooseLoose

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.

\"TheThe right way to check for an absent value

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)

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.

\"UndeclaredUndeclared variables

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)

This method works in every situation, but it is only preferable over value == null when you don’t know if value has been declared2.

\"TheThe problems with the other expressions

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)

\"AbsentAbsent object properties

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:

\"RecapRecap

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')

Footnotes

  1. MDN has some examples of when the void operator is useful. \"\"Back to content

  2. CoffeeScript follows the same principle when transpiling its existential operator to JavaScript. \"\"Back to content

","image":"https://tomeraberba.ch/checking-for-the-absence-of-a-value-in-javascript.png","date_published":"2018-08-16T00:00:00.000Z","date_modified":"2023-01-11T00:00:00.000Z","tags":["code","javascript","nodejs"]}]}