Faking type declarations in IntercalScript
created: 14.08.2020
INTERCAL is an esoteric programming language created in the early 70s to parody the programming languages of its time. IntercalScript is not based on INTERCAL. To quote the readme:
IntercalScript shares a name with INTERCAL for marketing reasons, despite the fact that the languages have absolutely nothing in common. If you’re feeling pedantic, you can name it after your favorite standards body and/or skin disease instead.
This text is about IntercalScript. As you can probably guess from this description, IntercalScript is the modern equivalent of INTERCAL. While it shares no syntax or features with it (except for the please
and do
keywords), it follows its spirit and relentlessly makes fun of current trends in programming languages. If you like that kind of humor, I highly recommend you check out the readme, I had a lot of fun with it.
IntercalScript compiles to JavaScript, is statically typed, uses excessive amounts of type inference, has sum types, uses error return instead of exceptions and is even null safe! It really is a summary of the last decade of programming languages.
While it’s obviously intended as a joke and some features (e.g. the undocumented please
keyword) and the lack of certain features (e.g. you can’t use parentheses for grouping in expressions) certainly make it a bit odd, it’s a surprisingly usable language.
Defining functions
Here’s the syntax for a simple function:
is-true = funct(b) if b then "yes" else "noooo" end end;
This is a dumb little function that takes a boolean and returns a string. Oddly enough, is-true
is a valid identifier. The funct
keyword is used to create a function. Like in scheme, there is no distinction between a named function and a lambda. I actually like this, as it makes the language simpler without losing any features. Since this is a statically typed function, your first instinct might be to ask what type declarations look like. Well, there are none. It’s impossible to declare types. Type inference is the only option. To make this easier, there is no operator overloading, e.g. +
is only for integers, while +.
is only for floats, like in OCaml.
At first, I wanted to try out whether IntercalScript has first-class functions. And it does! Here are some basic combinators:
apply = $funct(f,x) f(x) end;
ident = $funct(i) i end;
compose = $funct(f,g) funct(val) f(g(val)) end end;
Again, all types are fully inferred. For the is-true
function the type can be inferred just from the function definition. But these are generic functions, they are type checked when they are first used. Normally, the compiler caches the result: The types are fixed after the first use. That’s obviously not what you’d want for these combinators, so I put a $
in front of the funct
keyword to unlock let-polymorphism. Now the functions can have different types depending on context.
Interacting with JavaScript
After enabling the javascript ffi, you can just put javascript code in backticks straight into your code:
print(`/* this is js */ 5 + 4.5`);
The js is just copy’n’pasted into the generated code and evaluated where it is. Now… wait, what is happening here? IntercalScript can’t infer the return type of arbitrary javascript code, that’s impossible. So this isn’t sound. In fact, the documentation specifically warns that the javascript ffi is unsafe. Normally, you’d expect there to be a way to declare the types of the js code, similar to the way typescript does it. But no, IntercalScript has no type declarations at all. The creator of IntercalScript found a novel way around this. Let me use the example from the readme:
parse-int = `s => parseInt(s, 10)`;
I think this works similar to typescript’s any
type: parse-int
can now be used however you like, and the compiler won’t complain. If you call parse-int
it will also return such an any
type. You can use the result just fine, but all type safety completely goes out the window. The solution suggested in the readme uses dead code to force the compiler to assume the type:
parse-int =
if true then
`s => parseInt(s, 10)`
else
funct(s)
s +' "";
0/0
end
end;
Since if true
always evaluates to the first expression, parse-int
has the same value as before. But, since the type checker treats both branches equally, the type is now somewhat constrained. The else
branch returns a function that takes a string and returns a number, and the type checker applies these constraints also to the parse-int
function. Now parse-int(5)
would result in a compiler error, since 5
is not a string. parse-int("3").someCoolMethod()
will not work either, since the return type is a number. I think this idea is really cool, but the syntax is not super pretty. So let’s find a better way to describe this!
Playing with types
My idea is to describe each type with a function that takes a value of that type and returns it unmodified. For example, this describes numbers:
Number = funct(num) num +. 0 end;
This function has an inferred type of Number -> Number
and can be used like this:
print(Number(`5 + 4.5`));
By calling the function Number
on the js code, you can tell the compiler that it’s supposed to be a number. Easy enough. But since it’s possible to write functions that return functions, it’s also possible to create more advanced types from simple ones. For example, I wrote a generic Function
type, that depends on two other types. parse-int
now looks like this:
parse-int = Function(String,Int)(
`s => parseInt(s,10)`
);
As a reminder: Function
isn’t really a “generic type”, it’s simply a function that takes two identity functions and returns an identity function based on that. Function
just tricks the compiler into applying type constraints. To make sure that this doesn’t actually do anything at runtime, you can check the generated js code for Function
:
$Function = function(From, To) {
return function(f) {
return f;
};
};
But it does help us at compile time, and it looks much nicer than the alternative, IMO. Let’s just hope the V8 optimizes this away. With this simple idea it’s possible to describe types and enforce them at compile time. This can be extended do describe arrays, specific objects, tuples and combinations of these. After I played with these “types” for a while, I used them to write a small script that starts a web server and responds to requests. Thanks to the integration with node.js, getting a server running was very easy, but I did get stuck with my “type declarations” a few times. The compiler errors aren’t super good. But most of the time they were good enough, so I’m not complaining.
You can look at the code I wrote in this Github Gist. types.ics contains the type helpers and server.ics the code to get the server running. I decided to write all “types” capitalized. I invite you to play around with the types, and try to build an abstraction based on it. (Can you describe a recursive data structure, like a tree? Is it possible to fake a form of inheritance?)
My thoughts
Overall, I enjoyed using IntercalScript and trying to cheat the compiler. It works surprisingly well, especially the type inference is very powerful. And that’s actually something I’d like to criticize: It’s too good.
If the intention of this language is to be a parody of other languages, it should feel familiar, but annoying. The original INTERCAL was annoying. But the creator of IntercalScript really went all in with the type system, to the point where I almost feel like I’d rather see a serious language with this approach. The readme makes fun of valuing simplicity above all, stating that IntercalScript is “at least five times simpler than Go” and mentioning “orthogonality of concepts” as a reason that IntercalScript doesn’t need any more features. This is a bit contradicted by the fact that these arguments are somewhat reasonable. It doesn’t have type declarations, but you can kinda hack them in. It doesn’t have exceptions, but thanks to the js ffi, you could probably implement that as well. So it partially proves the point that it’s making fun of.
The readme references Stephen Dolan’s PhD thesis on subtyping, and I assume that it had an effect on the language. Maybe the creator started IntercalScript as a joke, but after a while got lost in trying to make it actually nice?
Type inference is nice, tuples are nice and sum types are nice, so it’s not an overall bad language. Sadly, due the deliberate anti-features, like having to write ().0
, I really wouldn’t recommend it for anything over a 100 lines. But if you for some reason have to write a little bit of throwaway node.js code, using IntercalScript could make it less boring. I had fun with it! Either way I recommend looking at the readme.
Also, the code formatter is very good.
Hi, feel free to reach out if you have any questions. But if you need any more specific advice, hire me at SpecificAdvice.com