Another ‘toot that became too long’ post, this time about dynamically vs strictly typed languages. Vapour is a new language that just dropped that has strict typing and transpiles to R (dynamic types) which means developers can write and refactor code with all the benefits of a type-checker and still end up with plain R code. I love the idea and have been playing around with it pretty much since I heard about it.
Type checks aren’t completely new to R - there are packages to include type annotations; {typed}, {types}, and others, but a new language layer (very much in the style of what Typescript brings to Javascript) is a cool new approach.
The language is very new, so there’s bugs, but a lot of things work (well) so there’s more than enough to do some pondering about type safety.
In this post I compared a full toy project in a strictly typed language (Gleam) vs R and was left a little underwhelmed by the benefits of type safety - I couldn’t identify a line of the code where I thought “type-safety saves the day right here” and in the release blog post for Vapour John spells out what I think I overlooked - the type safety helps you build the code properly, and connect the functions together in the correct way, and think about whether values might be missing or not, but a really significant benefit comes when you have already built something and want to change it.
I didn’t refactor anything in the Gleam vs R post, and if I did, I suspect that is where the ‘saved the day’ moment would have come.
A friend of mine presented a talk recently about functional programming and made the analogy of function return types to Lego bricks; the right combinations of studs (types) help you (the developer) understand which functions can be joined together. In terms of refactoring, replacing a piece of Lego with a particular shape means finding all the right connections on all the sides, otherwise it all falls apart.
Looking back to the Gleam post, if I wanted to change the Config
then all the places where that type is used would be checked to ensure that it still makes sense to have the structure I changed to. Without type checking, that could get quite involved, indeed.
One thing that the Vapour approach doesn’t give you (and neither does Typescript) is runtime type checking. The code generated by the transpiler has no type annotations (because R/Javascript don’t have them) so, while the internal functions to a package can be validated to work together, once you allow a user (or third-party developer) to interact with that code, all bets are off. I initially wrote in my Gleam vs R post about the need for defensive programming - lots of assertions at the top of a function body to assert that the inputs are as expected - and I think this still applies because they need to be runtime checks. Now, that leads to the question of whether something like Vapour could add those given that it needs to have defined types to compile, so part of that could be to just insert a stopifnot(is.character(arg1))
into the generated function body. Sure, you might prefer a checkmate::assert_string(arg1)
but maybe that could be customisable.
Lots of fun ideas to play with! John has done an amazing job with Vapour - I’ve been digging through the Go code to play with things and there’s a lot of work that’s gone into this. Playing with this has really helped me understand specifically where the benefits of type safety are.