— 12 min read
Solving the first three SICP problems with Airscript
Over the past year, I’ve been doing a lot of building in Airkit. This is largely because, as of March of last year, I work at Airkit, and so I need to be familiar with the platform for professional purposes. But it’s also because building in Airkit is a lot of fun. There’s a lot you can make with Airkit, but I get the most joy out of putting together things that Airkit wasn’t really made to support. (Airkit is optimized for creating digital customer experiences, like call deflection or WISMO apps, but did you know you can also use it to throw together a copy of Wordle in about two hours?)
I’ve also been going through the second edition of Structure and Interpretation of Computer Programs, or SICP, by Harold Abelson and Gerald Jay Sussman with Julie Sussman. This is not actually for professional purposes. I just think SICP is neat. It was first written in 1984 as an introduction to Computer Science, and it uses Scheme, a dialect of LISP, to introduce patterns of programmatic problem-solving. I’ve learned a lot about programming by solving the practice problems provided by SICP (and I’m not the only one – SICP is still known as the “Wizard Book” in Hacker Culture).
Naturally, I found myself asking the question: “Can I use Airkit to solve the practice problems from SICP?”
Airkit is a low-code platform, which makes it sound unconducive to solving the deep programming problems provided by SICP – or at least, solving them in any meaningful way. But while it is true that Airkit is not intended to showcase the under-the-hood processes of computers (in the same way it is not intended to recreate games like Wordle), low-code is not no-code, and, as it turns out, you can solve some SICP problems using Airkit’s internal programming language, Airscript.
Scheme vs. Airscript
Airscript was created to supplement Airkit’s low-code platform, primarily for the purpose of data manipulation. It is a much newer language than Scheme: Scheme has been around for decades, while Airscript (and the low-code platform in which it resides) is not even a decade old. Consequently, Airscript was built on a foundation of faster computers capable of interpreting instructions given at higher levels of abstraction.
At the time of its creation, the structure and syntax of Scheme was considered intuitive. To this day, it is still comparatively simple, which makes it a good language to use when introducing programming in a way that focuses on underlying patterns rather than the details of language syntax. Simple is good for computers. When you remove all the layers of abstraction, computers can only perform simple tasks.
Airscript is less simple, because it was created decades after Scheme’s creation, and simple is not the same as intuitive to most humans.
Question 1.1: Comparing Basic Syntax and Defining Variables
The first question in SICP is meant to gently introduce procedures and operators. It provides several lines of Scheme and asks readers to predict what the code will return before running the code to check their predictions.
Scheme and Airscript do not have the same syntax, so to run the equivalent procedures in Airscript, I had to first translate the given lines of code into something Airscript-parsable. The following table shows the first five procedures given by SICP in Scheme, how those same procedures translate to Airscript, and the result both the Scheme and Airscript procedures have in common:
In comparing equivalent Scheme and Airscript expressions side-by-side, we can see how the contrasting natures of the two languages manifest.
For instance, look at the placement of the operators. In Scheme, operators are always placed at the beginning of the relevant expression. In Airscript, the placement of operators mimics their placement in arithmetic equations. Scheme is meant to provide straightforward instructions to a computer, and so the standardization of leading operators is the most straightforward way to structure the syntax. (We’ll see more of this in problem 1.3, when we define our own procedures.) Airscript, meanwhile, is meant to supplement a low-code platform, and so the placement of operators makes the procedures easier for humans – even people relatively unfamiliar with programming – to skim.
Scheme also requires far more copious applications of parentheses. This is something LISP and all its derivatives are famous for. The name LISP derives from LISt Processor; its syntax consists of lists and nested lists, each designated by their own sets of parentheses.
In Scheme, lists consist of an operator, which is always followed by the operands on which it acts. Airscript, meanwhile, is more reminiscent of human language in that its syntax is less rigid. Its syntax consists of more than lists and varies depending on the nature of the given procedure. Consequently, when designating these sorts of mathematical expressions, parentheses are used only to indicate when the enclosed portion ought to be performed before regular order of operations would dictate.
The next two lines of Scheme code provided by SICP define two variables, a and b. In LISP, this is done programmatically, as follows:
(define a 3)
(define b (+ a 1))
Neither of these lines returns any output. The first defines a variable a so that it is equal to 3, and the second defines a variable b so that it is equal to a+1, which, because a was defined as 3, sets b equal to 4. The values of these variables can now be referenced in downstream procedures.
Airscript has no direct equivalent to these procedures. While the Airkit platform allows you to define variables, it is not done directly in lines of code. (This is where the “low-code” part of Airkit’s low-code platform comes in.)
There are multiple ways to define variables and assign them values in Airkit. When running procedures as part of a larger application, a common practice is, for instance, to define variables in the Variable Tree and then use the Set Variable Action to bind that variable to a value based on input given by the user. However, in the spirit of this exploratory, introductory question, I opted to create a new Data Flow for testing purposes, set it to expect a single number, a, as input, and then gave a the dummy value 3.
From there, I defined b in a Transform Data Operation as a + 1:
This gave all downstream Data Operations within this Data Flow access to the variables a and b, which, while run here in the Connections Builder, equaled 3 and 4 respectively. This provides the foundation to run Airscript procedures that call on these variables and see them behave in an equivalent manner to Scheme functions calling on the same variables. Once variables have been defined, Scheme and Airscript reference their values the same way the vast majority of programming languages do:
The last expressions given by problem 1.1 in SICP showcase primitive Scheme operators beyond the ones that do basic arithmetic: if and cond. In Airscript, analogous functionality is achieved by the function IF, which accepts input analogously to the cond operator in Scheme. Note again the effects of the differences in syntax. In Scheme, the placement of operators stays rigid regardless of the nature of the operator, whereas in Airscript, functions behave very differently from arithmetic operators. The IF function proceeds its operands, and while parentheses are used to designate all function input, the different given values are separated by commas. Again, Airkit demonstrates that its syntax is less uniform but easier to skim.
Question 1.2: Generating Procedures
The second question given by SICP asks you to translate the following mathematical expression into code:
In Scheme, this is done as follows:
(/(+ 5 4 (- 2 (- 3 (+ 6 (/ 4 5))))) (* 3 (- 6 2) (- 2 7)))
(This returns the value -0.24666666666666667)
And in Airscript, it looks like this:
(5 + 4 + (2 - (3 - (6 + 4 / 5)))) / (3 * (6 - 2) * (2 - 7))
(This also returns the value -0.24666666666666667)
Again, in comparing equivalent Scheme and Airscript expressions side-by-side, we can see how the contrasting natures of the two programming languages make Airscript more legible and Scheme more technically straightforward.
It’s also more obvious that, when using Airscript, arithmetic operators can only act on single pairs of numbers. Writing a long summation in Scheme entails writing a single operator (ie: (+ 1 2 3 4 5)), whereas writing the same summation in Airscript can get repetitive (ie: 1 + 2 + 3 + 4 + 5).
To avoid this, we could use some of Airscirpt’s out-of-the-box functions; Airscript comes with far more to start with than Scheme. The SUM function in Airscript takes a List of Numbers and adds them all together. (As a side note, the SUM function can also take a List of Currencies and add them together; part of the benefit of using a low-code platform is that it can abstract away some of the details of data types.) The PRODUCT function in Airscript takes a Lists of Numbers and multiplies them all together. So if, for instance, you wanted to write out the mathematical expression without repeating operators, more similarly to how Scheme does it, you’d write it like so:
SUM([ 5, 4, 2 - (3 - (6 + 4 / 5)) ]) / PRODUCT([ 3, (6 - 2), (2 - 7)] )
Given that, in this particular case, both the SUM and PRODUCT functions are only given Lists containing three items apiece, I don’t think this makes it more particularly short, legible, or worth it, but it is nice that Airscript gives you the option in the hypothetical case of longer equations.
Question 1.3: Defining a Procedure vs. Creating a User Defined Function
The third question in SICP tasks you with defining a procedure that takes three numbers as arguments and then returns the sum of squares of the two larger numbers.
In Scheme, new procedures are defined using the same define operator that is used to define variables.
Basic Scheme comes out-of-the-box with relatively few primitive operators. There is no operator that squares a number, no operator that calculates the sum of squares of two numbers, and not even an operator that checks to see if one number is less than or equal to another. If we want to use procedures that do any of these things, we must define them first. Thus, defining this new operator (which I called larger-sum-squares), must be done as follows:
(define (square x) (* x x))
(define (sum-squares x y) (+ (square x) (square y)))
(define (<= x y) (not (> x y)))
1 2 3 4
(define (larger-sum-squares x y z) (cond ((and (<= x y) (<= x z))(sum-squares y z)) ((and (<= y x) (<= y z))(sum-squares x z)) (else (sum-squares x y))))
We can run a quick test to see if this operator works as intended. For instance, the expression
(larger-sum-squares 1 2 3)
returns 13, as expected.
When working in Scheme, operators, like variables, are defined entirely programmatically.
Airscript provides no functions that allow you to define operators, much like how Airscript provides no functions that allow you to programmatically define variables. You can, however, still create your own functions – just not programmatically. Airkit is a low-code platform, and so custom functions, called User Defined Functions or UDFs, are defined in the Connections Builder, under User Defined Functions.
(I’m going to touch briefly on how I made my UDF, but if you want to dive into more detail on how UDFs are defined, you can check out the guide Creating Custom Functions or, if you prefer video tutorials, the analogous Building Byte.)
I named my new UDF largersumofsquares and defined it so that it expected input in the form of three numbers, designated x, y, and z:
From there, I defined the behavior of my new UDF by writing Airscript in the Function Body.
Unlike when defining this procedure in Scheme, I did not have to define any other functions beforehand. Airscript comes with much more complicated functionality out of the box, including the function SUMSQ, and the comparison operator <=. Consequently, the defined procedure in the Function Body simply read as follows:
1 2 3 4 5 6 7
IF( x <= y AND x <= z, SUMSQ(y, z), y <= x AND y <= z, SUMSQ(x, z), SUMSQ(x, y) )
We can run a quick test to see if this UDF works as intended. For instance, the expression:
largersumofsquares#USER_FUNCTION(1, 2, 3)
returns 13, as expected.
I had a lot of fun solving these, and I’m hoping to tackle more SICP problems with Airscript in the future. There’s a lot more to dive into that the first three problems don’t cover!
In the meantime, if you want to try solving SICP problems with Airscript yourself, you can find a full, open-source copy of SICP here, and you can get started with the Airkit platform here. If you learn something interesting – or if questions come up – post about it on our community forum. I, for one, would love to hear about it.