Andrew Chan


The Skew Programming Language

In early May, the blog post about one of the most interesting projects I worked on at Figma - the quest to migrate away from our custom programming language - was released!

I won't talk more about the automated migration we did or the history behind Skew, except to say that it was a joy working with my coworker Brandon, who led the migration project and is a prolific compiler hacker and engineer. The post on the Figma blog that we wrote together contains lots of gory details (with plenty of Hacker News comments), and my Twitter thread has some more history.

Instead, I'll write a bit about the Skew programming language, which, by the way, is open source and created by Evan Wallace, who published longer documentation and a live demo on his website.

Interesting features of Skew

At a high level, Skew is a fairly conventional object-oriented language. Like Typescript, it has first-class functions (including lambdas), classes, interfaces, and basic generics. It's statically typed, though unlike Typescript it lacks structural or algebraic types, though it does have nominal types, which TS doesn't have. The type system is overall stricter: there is no monkey-patching of objects, no object prototypes, and the types are sound, which means a value is guaranteed to be of the type it's declared as at runtimeUnless the dynamic escape hatch is used..

Beyond these basic features though, there are a few interesting ones. Let's go through them.

Wrapped types

This is a feature that I hadn't encountered before. I did learn recently that it's similar to Swift's extension and Haskell's (and Rust's) newtype. The idea is to allow extending type aliases with "methods" that are converted to global functions at compile time. This is especially useful for aliases of primitives like int or double, since you can basically treat them as zero-cost abstraction objects in your code.

For instance, here's the example from the Skew docs of a Color type encapsulating 8-bit RGBA which is just a 32-bit integer under-the-hood:

            
type Color : int {
  def r int { return ((self as int) >> 16) & 255 }
  def g int { return ((self as int) >> 8) & 255 }
  def b int { return (self as int) & 255 }
  def a int { return (self as int) >>> 24 }

  def opaque Color {
    return new(r, g, b, 255)
  }
}

namespace Color {
  def new(r int, g int, b int, a int) Color {
    return (r << 16 | g << 8 | b | a << 24) as Color
  }
}

def main {
  var c = Color.new(128, 0, 0, 255)
  dynamic.console.log(c.r)
}
            
        

This compiles to:

            
function main() {
  var c = Color.$new(128, 0, 0, 255);
  console.log(Color.r(c));
}

var Color = {};

Color.r = function(self) {
  return self >> 16 & 255;
};

Color.$new = function(r, g, b, a) {
  return r << 16 | g << 8 | b | a << 24;
};
            
        

Real integer types

As explained on Twitter, Skew has a real int type. All modern browser engines have some optimized version of the JS number type which can be used as long as a number value stays within the 32-bit integer range. This was a side effect of the asm.js standard, and it's something Skew was able to leverage by compiling all operations on int values to their asm.js equivalents. For instance, this snippet of code:

            
var x int = 5
var y = x*424242
var z = y/4
            
        

Compiles to this JS:

            
var __imul = Math.imul ? Math.imul : function(a, b) {
  return (a * (b >>> 16) << 16) + a * (b & 65535) | 0;
};

var x = 5;
var y = __imul(x, 424242);
var z = y / 4 | 0;
            
        

Dart is the only other compile-to-JS language that I've seen also have an int type, but the ergonomics are looser: for example, dividing with the / operator always returns a double (there is a special integer division operator ~/).

Compare the Skew code:

            
var x = 42
var y = x / 5
dynamic.console.log(x) # 42
dynamic.console.log(y) # 8
dynamic.console.log(y == 8) # True
            
        

To this Dart code:

            
var x = 42;
var y = x / 5;
print(x); // 42
print(y); // 8.4
print(y == 8); // False
print(x ~/ 5 == 8); // True
            
        

Flags

These are syntactic sugar for bitflags. So

            
flags DirtyFlags {
  TRANSFORM
  BOUNDS
  GEOMETRY
}

var flags DirtyFlags = .TRANSFORM | .GEOMETRY
            
        

Translates to the following JS:

            
var DirtyFlags = {
  TRANSFORM: 1 <<< 0,
  BOUNDS: 1 <<< 1,
  GEOMETRY: 1 <<< 2
}

var flags = DirtyFlags.TRANSFORM | DirtyFlags.GEOMETRY
            
        

This is a very simple feature, but I haven't seen it anywhere else (you can define custom enum variants in many other languages, but it's not the same).

Customizable syntax

Some syntax in Skew is customizable. For example, there is fairly comprehensive operator overloading (which Dart and many other languages also have). This made working in graphics code which heavily used vectors a joy: writing A * (v2 - v1) + foo(-v3) is much nicer than A.mul(v2.sub(v1)) + foo(v3.minus())!

One of the more interesting features of Skew is custom List/Map/XML literals. Though we pretty much never used this, it was supposed to allow for ergonomic custom data structures. For example, see the custom Map snippet from the docs:

            
class DemoMap {
  def {...}(key int, value int) DemoMap { return self }
  def {...}(key string, value string) DemoMap { return self }
}

var map DemoMap = {1: 2, "3": "4"}
            
        

Custom XML literals were supposed to allow for JSX-like code:

            
class Div {
  var name string = ""
  var id string = ""
  var _children List = []

  # The compiler uses this function to append children
  def <>...</>(child Node) {
    _children.append(child)
  }
  
  def html string {
	  return (
		  "<div>" + 
			"".join(_children.map(c=>c.html)) + 
			"</div>"
		)
  }
}

var tree = (
	<Div name="foo" id="1">
		<Div name="bar" id="2">
		</Div>
		<Div name="baz" id="3">
		</Div>
	</Div>
)
dynamic.document.body.innerHTML = tree.html
            
        

Name-based member access

Variable and method names starting with _ are automatically protected (only made accessible to a class and its subclasses). For example:

            
class Foo {
	var _bar = 5
	def baz {
		dynamic.console.log(_bar) # ok
	}
}

def globalFunc(x Foo) {
	dynamic.console.log(x._bar) # error
}
            
        

I thought this was an excellent way to make the naming convention part of the language, but the implicitness made things harder for developers who didn't regularly work with Skew.

Open declarations

Another unique feature of Skew is that all declarations are visible everywhere. This means there is no such thing as a module system where files can choose what to expose, and there are also no import statements. Moreover, class and namespace declarations were "open" like in Ruby, meaning that members from duplicate declarations in different files were merged together at compile time. For example:

            
####### foo1.sk
class Foo {
	var _bar = 5
    def bar {
        return _bar
    }
}

####### foo2.sk
class Foo {
	def baz {
        return bar + 1
    }
}
def test(x Foo) {
	dynamic.console.log(x.baz) # returns 6
}
            
        

In retrospect, I think this made for some fairly messy code, but it was good for productivity.

Compiler optimizations

There's some more detail about this in the Figma blog post, but one of the key advantages of Skew for us was that it has an optimizing compiler that can perform advanced optimizations like devirtualization, inlining, and more aggressive constant folding and dead code elimination. The reason we don't see these optimizations often for compile-to-JS languages like Typescript is that often languages attempt to keep the dynamism of JavaScript, which makes applying certain optimizations unsafe (in the face of things like monkey-patching or unsound type systems).

Conclusion

Skew was (is) an incredible achievement for what started out as a hobby language while the author was also founding a company:

But supporting an in-house programming language is a huge ask if you're not a big company. I don't know of any company that has done so successfully over the long run: Naughty Dog moved away from GOAL, Fog Creek killed Wasabi, etc. The companies that have sustained languages that aren't widely used still saw them adoped by outside developers. For instance, while Jane Street is famous for using OCaml, it's also used in plenty of other places, like the Coq and Unison projects and at Facebook and Bloomberg.

The problem is ultimately social, not technical. In other words, it's not just that your in-house language doesn't support XYZ feature, but that

At Figma, we actually recognized this early on, and Evan began efforts to move off Skew in late 2019/early 2020. But we didn't do it for real until a few years later. All Skew code at Figma is now Typescript, an excellent language which is absolutely the right choice both for the company and for the product being built. Maybe in the future I'll write a bit about some of my favorite TS features.

Appendix

We wrote Skew every day at Figma for years, so we had the largest Skew codebase by far. But if you're curious what a large codebase looks like, check out Evan's projects on GitHub: