Designing programming languages with IDEs in mind
In this post we’ll discuss how having the way users write code in mind affects the design of programming languages, and how it affected our own decisions when designing the language for Lamdu.
We will start by discussing a feature called named parameters.
Smalltalk is a programming language with named parameters. Here’s an example to illustrate what that means:
Rectangle left: 50 right: 80 top: 100 bottom: 170
NSMakeRect (50, 100, 30, 70)
Smalltalk was designed to be used via its IDE, which was released with it (an editor specifically designed to edit code in Smalltalk).
The C code can be written on a whiteboard more quickly than Smalltalk, but writing the code on the computer takes the same effort for both languages — when using Smalltalk’s editor the argument names are auto-completed.
Which version of the code is easier to read? We’re favoring Smalltalk. What does each of the four arguments in C mean? Left? Right? Width? Height? Center positions? Only a programmer who is already familiar with that library function will know what they mean without needing to have a look at the documentation. This makes C require a steeper learning curve than Smalltalk, where the named parameters make the code self-documenting.
We believe that Smalltalk’s designers made the right design choice for function call syntax, but that the designers of C have also made the right design choice.
How can that be? When C was released in the early 1970s people used generic text editors to edit C code. And typing the parameter names in such editors does require effort, so C’s designers made the right choice given the tools available to their users (IDEs were not prevalent at the time).
IDEs helping after the fact
Some editors display popups when hovering over function calls to show the function’s documentation. That’s certainly useful, but the Smalltalk reading experience is still superior, as one can simply glance at the code instead of moving the mouse pointer and reading what pops up. This also makes for an easier pair/group programming experience, as programmers not holding the mouse still see the relevant info.
IDEs can be better than generic text editors, but an IDE and a language designed in unison can make for an even better experience.
Notable programming languages with named parameters
- Objective-C: Released with an IDE.
- SWIFT: Supports both conventions, also released with an IDE (The SWIFT term is External Parameter Names).
- Python supports both conventions and leaves the function’s caller the choice of which convention to use (The Python term is keyword arguments).
Explicit lazy evaluation
Again, we’ll explain what this means with a code example.
Lamdu: x == 3 || ◗ x % 5 == 0
Haskell: x == 3 || x % 5 == 0
C/C++: x == 3 || x % 5 == 0
Python: x == 3 or x % 5 == 0
Smalltalk: x = 3 or: [x \\ 5 = 0]
In all examples above, the “logical-or” operator (“||”) short-circuits. That means that the computation on its right hand side does not get evaluated if the expression on its left hand side evaluates to “True”.
First let’s describe the difference between the Haskell and C++ examples above, which on the surface look exactly the same:
- In C++ the “||” operator is a built-in language primitive and such operators cannot be defined by users or libraries. In C++, user-defined operators and functions do not support short-circuiting (The same holds for Python).
- In Haskell, the “||” operator is actually defined in the standard library (it can be implemented by users). Haskell supports this because it has “pervasive lazyness” — this means that expressions only get evaluated when their values are needed. Instead of getting the values of the arguments to a function, it actually gets “thunks”, which can be evaluated to get the value if and when it is necessary.
Pervasive laziness has its advantages, but it has disadvantages too, and that’s why it isn’t a common feature in programming languages.
We believe that we can get the best of both worlds with explicit laziness and proper IDE support.
The logical-or operator in Lamdu gets a boolean on its left side, and a “deferred computation” (aka function with no arguments) of a boolean on its right side. The syntax for these computations is very lightweight — the ‘◗’ symbol preceding the computation, with parenthesis for disambiguation when necessary (the Smalltalk syntax for this is square brackets around the expression).
This choice does not make writing code harder, as Lamdu’s editor completes this pattern with the ‘◗’ symbol automatically.
Records vs tuples
Imagine we have a function that performs a calculation and returns two values: ‘speed’ and ‘azimuth’.
In most programming languages, we may either return an anonymous tuple “(speed, azimuth)” or use a record type for them. Each approach has its own advantages.
Advantages of tuples:
- We don’t need to remember the exact name of the field — whether it was ‘azimuth’ or maybe ‘angle’? An IDE would solve this problem by offering field name completions.
- We avoid the laborious type declaration ceremony. In a language which has a structual type system with type inference, these type declarations are not necessary.
Advantages of records:
- We don’t need to remember the order of the values, so there’s no risk of confusing them.
- In statically types languages, using records makes programs more reliable by making better use of the type system for distinguishing different things with types, and also provides more info about functions in their type signatures.
Lamdu provides the best of both worlds: Its type system and IDE features bring the advantages of tuples to records. Records in Lamdu keep the reliability and readability of records but gain the ease and convenience of tuples.
Thus, tuples are no longer necessary, so Lamdu only has records (tuples can still be implemented as degenerate records like C++’s std::pair).
Having an IDE affects the trade-offs of programming language design.
In Lamdu we want to create the best programming experience possible. As we aim for a “next-gen” IDE, and as no programming language was yet designed for one, we’ve decided to design a new language for it, resembling an existing language (Haskell) but with some modifications due to the different design trade-offs given the different environment users will use to write the code.
Note that Lamdu’s design is not final and is still incomplete. We would definitely like feedback on the topics discussed here.
The general rule seems to be: When designing a system one should consider how the different components interact.
In a future post we’ll describe another instance of this rule — how editing code as an AST (rather than a string of characters) enables Lamdu’s novel approach for type errors.