Felix' Workshop

Expression Problem Bench

A program is a grid. Down one side are the cases it has to handle; along the other are the things it has to do with them. Every cell is one expression, and a language does not get to decide what goes in them — only how they are boxed up. Objects draw the boxes across the rows. Functions draw them down the columns. Same grid, same cells, same answers; two arrangements, and each one is easy to extend in exactly the direction the other is hard.

That is the expression problem, and the table is the whole of it. Neither arrangement is behind the other: one is cheap in the direction the other is dear, and the numbers cross over as the grid grows.


  

Generated from the cells above, not written out twice. Press the other arrangement and every character in the boxes stays the same; only the scaffolding around them moves.

Focus any cell in the grid to walk that one instead.

by case


        

by operation


        

The class knows its own methods, so the lookup is one hop whatever the case is. The match has to ask, and it asks its arms in order, so a case near the bottom of the list costs more questions than one near the top — walk a triangle and then walk a circle. Both roads end on the same line.

The Python is read rather than mimed. python.js is a small one written for this page — a lexer, a Pratt parser over the expression grammar a cell is allowed, and an evaluator that keeps ints and floats apart the way Python does, so b * h / 2 comes back 6.0 and not 6. The same token stream that runs a cell is the one that colours it, so a thing painted as a number is a thing the evaluator read as a number. selftest.js checks it against CPython's own answers, in the browser at tools/verify and under node with node expression-problem/selftest.js.

Only expressions, which is all a cell is ever allowed to be: names, attributes, calls, numbers, strings and f-strings, the arithmetic and comparison operators, and not, and, or. No statements, no lambdas, no comprehensions. A cell that reaches past that gets a parse error where its answer would be, which is the honest report. Transcendental functions come from the browser's own library rather than CPython's, and the two are entitled to differ in the last bit.

The cells are yours to edit. Anything you type is an expression over that row's own field names, which is why one cell can serve both arrangements without being rewritten: the class binds those names off self, the match binds them in its pattern, and the expression in between never learns which happened.