Extending ts-wolfram: dropping OOP , kernel/userspace interop, better printing
I mentioned before that getting an interpreter working is a nerd-snipe. Once it works a little, it’s so much fun to keep adding functionality you end up working on it despite yourself. In this spirit, here is a writeup about my latest changes to ts-wolfram.
Dropping OOP
I initially wrote the interpreter using OOP. There were three AST types ( Integer , Symbol , and Form ), each one derived from a Node interface. This worked but bothered me, primarily as an aesthetic matter. I prefer to think of programs in terms of pipelines of transformations between data types. You can do this with classes, but stylistically it doesn’t quite fit and tends to leave a feeling of dissatisfaction. So I got rid of classes in favor of an algebraic data type, which to me looks much nicer:
export type Expr = Integer | Symbol | Form | String ; export enum Types { Form, Integer, Symbol , String , } export type Form = { type : Types.Form, head : Expr, parts : Expr[], } export type Integer = { type : Types.Integer, val : number , }
Many small changes downstream of dropping OOP also made the code much nicer. For example, I got rid of ugly instanceof calls. But overall, code organization could still use one more pass of deliberate thinking through the file structure, and putting code that belongs together in dedicated files.
Kernel/userspace interop
Second, I changed the interpreter to support mixing “kernel” and “userspace” code in expression evaluation. Previously a function could either be defined in Typescript, or in Mathematica code, but not both. For example, Times is defined in Typescript, which meant I couldn’t put convenient transformation code into the prelude. Typing -a (-b) in the REPL produced Times[Minus[a], Minus[b]] . It would be convenient to add a rule to transform this into Times[a, b] , but the evaluator didn’t support that.
Supporting this ended up being a small change. Once the evaluator supported mixed definitions I added the following line to the prelude:
Times [ Minus [ x_ ] , Minus [ y_ ] ] := Times [ x , y ] ;
... continue reading