Descopes: A missing compiler feature

Greg O'Connor
1 min readJun 7, 2017

--

The level of safety and convenience afforded by modern languages is at an all-time high, and improving rapidly. However, one feature I have yet to see in a language is the ability to descope variables manually for the current scope. Consider the following snippet of Typescript:

function renderPages = (pages: Page[]) => {
console.log(`Rendering ${pages.length} pages`);
return pages.map(this.renderPage);
}

Nothing much going on, but later its decided that we should actually only render non-empty pages. The programmer happily jots down the following change:

function renderPages = (pages: Page[]) => {
const visiblePages = pages.filter(page => page.length > 0);
console.log(`Rendering ${visiblePages.length} pages`);
return pages.map(this.renderPage);
}

Aaaaand we have a bug. While the programmer has filtered the pages, they’ve forgotten to actually use the filtered variable instead of the unfiltered version. The type-checker isn’t much help in cases like these, since the new variable has the same type as the old. (side note: please can we have nominally typed aliases?)

In comes descopes: a directive I’d like to see in imperative languages which lets us easily take a variable out of scope for the remainder of the current scope.

How might it work?

function renderPages = (pages: Page[]) => {
const visiblePages = pages
.filter(page => page.length > 0) descopes pages;
console.log(`Rendering ${visiblePages.length} pages`);
return pages.map(this.renderPage); // Compiler error!
}

Of course half-decent tests would have caught the above bug, but descopes enshrines the semantics of what we are doing here in both for the compiler and future programmers without the indirection of introducing a new function.

--

--