Widgets
To try all of these live, visit the Widget MenagerieNumbers
Strings
Data
Objects
Basic Widgets
Miscellaneous
Making Your Own Widgets 🎂
Carbide widgets are just react components (what's a react component) with a static class propertytitle
and a static member function called match, with the signature match(value, ast)
. Whenever you open a widget on some expression, carbide runs the expressions value and ast through all of the installed widgets' match functions to figure out which widgets it should show in the tabs.
If you have a react component with a static title and match function, you can add it to your notebook like this:
import React from 'react'
class YOLOWidget extends React.Component {
static title = '#YOLO'
static match(value, ast){
return value === '#YOLO'
}
render(){
return <a href="https://en.wikipedia.org/wiki/YOLO_(motto)">
https://en.wikipedia.org/wiki/YOLO_(motto)
</a>
}
}
doc.kernel.widgets.push(YOLOWidget)
Backpropagation
Numbers
For the mathematically minded, you can interpret the entire program as a function whose arguments are the literals in the program, and whose output is the particular inspected expression. The challenge is then to find an assignment to the arguments, such that the output is as close as possible to the desired value. Once that's done, you can substitute those literals back into the code, and you've assembled a new program whose output at that arbitrary expression is what you told it to do.
In other words, we're trying to solve for the inverse of a function. Another equivalent (more specific) framing of the problem is that we're optimizing a cost function (the distance between the output and the desired target output) over a space of parameters.
Fortunately there's a massive amount of literature available about the problem of unconstrained (or constrained!) minimization. We're currently using a version of NumericJS's uncmin function, which implements a quasi-newton method (similar to L-BFGS), which is a generalization of Newton's root-finding method to multiple parameters.
Quasi-Newton methods need to calculate the gradient of a function at each step to determine which direction to travel toward. Currently this entails evaluating the function several times with slight tweaks to the existing parameters (in the future one could imagine implementing automatic differentiation so one would only need to evaluate once per step).
In case the optimization engine ever gets stuck in a place with zero gradient, the previous working parameters are remembered, so each interaction can start with the closest known reasonable point (We know it affectionately as Guillermo "Memo"'s Memoization).
Strings
While there have been many decades of work done on improving numerical optimization, there isn't nearly as much work done on generalizing the idea to other data structures.
Nested Objects
IM GOING TO WRITE ABOUT THIS REAL SOON NOW
Declared Inverses
IM GOING TO WRITE ABOUT THIS REAL SOON NOW
Cascading Approaches
IM GOING TO WRITE ABOUT THIS REAL SOON NOW
Technology
There's a fair amount of tekmology and stuff that goes into Carbide, and to be totally honest, we totally pulled an all nighter trying to write copy for this website, and there's a lot of detail that we don't really have time to include. But hopefully over the coming weeks, we'll be able to publish some blog posts going into a bit more technical depth.
SystemJS/JSPM
NPM is probably one of the largest and most successful package managers ever created (albeit not without a few kerfuffles). So it was pretty important to be able to easily import modules from NPM.
Originally, we had written our own tiny implementation of NPM which would run in the browser (calling the official NPM API endpoint, locating tarballs, unpacking them in the browser, and loading the files appropriately). Separately, we were using Babel and Webpack to develop the application itself.
But over the past few months we discovered SystemJS, and ended up using JSPM to handle just about everything. In fact, nowadays Carbide's "Current Window" mode uses the same SystemJS loader for both the editor itself and the code which gets executed on it. This means that it'll technically be able to self-host, hot-reload, and instrument various parts of itself. That's kinda neat.
BabelJS
Most browsers don't quite support all the latest features and proposals that people have drafted up, so in order to use the latest and greatest syntax and semantics, people have to use programs called "transpilers" which translate the source code of one language and into another one.
But far more important— as far as Carbide is concerned, is just being able to have access to the abstract syntax tree of a chunk of code, and being able to instrument certain ranges.
Top-Level Async/Await
To make everyone's life easier, Carbide cheats a bit and allows you to use the await keyword in the top level, as if you were writing an async function. The way this works under the hood is basically by detecting top level awaits, and wrapping your entire cell in an async function if it finds them.
We've broken out the Babel transform that does this into its own repo.
Hot Reloading
Hot reloading is really neat, and it's rapidly become a staple of modern Javascript development (to whatever extent you can string those words together and pretend it's a phrase). But it's actually key to creating a fun interactive programming environment— being able to substitute modules without running everything from scratch.
To accomplish this we've built our own Hot Reloading engine on top of JSPM. It's less aggressive in reloading dependents than the SystemJS hot reloader and the Webpack Hot module replacement system. It exploits the fact that in ES6, references are "live" so imports can be updated simply by calling a getter.
However, sometimes you actually do want Webpack's aggressive reloading of dependents— this can be done by exporting a variable __forceReload
.
export const __forceReload = true
To clean up stateful code in preparation for a reload, export a function called __unload
that optionally takes the module compiled from the newer code.
export function __unload(new_module){
cancelAllEventListeners()
}
CodeMirror
When we started thinking about making Carbide self-hosting, we realized that it had to be made in such a way that it could handle large notebooks. Unfortunately if you use the HTML's natural layout mechanism to lay out your cells, everything still needs to be rendered on every keystroke— there's no way to do optimizations like rendering only the visible lines (because it's then the browser who figures out what a visible line is).
Carbide's current implementation is kind of strange in this regard because we have a single gigantic CodeMirror instance whose document represents the total concatenation of all the cells in the document. We have a special layout algorithm to insert vertical spaces where appropriate so we have enough space to paint on user interface elements on top.
This allows us to take advantage of CodeMirror's neat lazy rendering capabilities and to only render the things which are visible on screen.
Nondeterministic Layout Engine
To Enable Carbidiculous behaviors, like treating text contours as elements that can block floating widgets, Carbide's layout engine uses a form of McCarthy's AMB operator. Essentially, because Carbide need to calculate layout greedily, layouts sometimes have to exist in a superposition of two states, that can only be resolved after further layout calculations. This is sort of like quantum bogo sort for css.Future
Preview: Adding Custom Kernels
Although Carbide is Javascript-first, parsing, compiling, and running code are all implemented as external modules. This means that it's possible to define a javascript module that replaces the carbide kernel in order to run code in a different language. Here's an example of what a minimal kernel will probably lookl ike:export async function parse(code){
// Return some AST
}
export async function match(ast, code, start, end){
// Return closest valid expression
}
export async function evaluate(ast, cell){
// Evaluate the cell
}
Preview: Watlab Kernel
Watlab is compile-to-javascript language inspired by Matlab. Almost all of the syntax is the same as ES2015, with addition of a postfix function application operator, matrix literals, and standard matrix operators (e.g.A\b
to solve a system of linear equations).
Preview: Python Kernel
A tiny kernel that communcates with a local python REPL. Pros: filesystem access, cool python libraries. Cons: requires annpm install; npm start
command.
Preview: MIT Scheme Kernel
A tiny kernel that communcates with a local MIT Scheme REPL. Pros: filesystem access, arcane wizardry. Cons: requires annpm install; npm start
command, arcane wizardry.
Preview: Time Traveling
One of the features that didn't quite make it into the alpha release is a Bret Victor Style program execution scrubber that lets you get a more intuitive sense of stuff like for loops:[images]Stay Tuned.