In the previous section, I discussed aspects of simple code. However, it is also possible to consider simplicity in programming languages.
-
Is JavaScript a simple programming language?
-
Is JavaScript a good programming language?
Again, one way to think about a programming language is whether it has three attributes of simplicity:
-
Uses a small number of rules
-
Each rule is easy to understand
-
No (or very few) exceptions to the rules
Addition and concatenation
To explore simplicity, consider the +
operator that performs both addition and string concatenation in many programming languages. When given two numbers, +
typically performs addition. When given two strings of text, +
typically performs string concatenation.
The following table demonstrates the effects of the +
operator:
Code |
Result |
Operation |
---|---|---|
|
|
Numerical addition |
|
|
Numerical addition |
|
|
String concatenation |
|
|
String concatenation |
This table is valid for many programming languages, including JavaScript, Python, Java, Ruby, Go, C# and SQL.
So far, the rules underpinning this table are simple: numbers get added, strings get concatenated.
Simultaneously handling strings and numbers
What should be the result of using +
on different types? For example, what is the result of adding the number 1
to the string "2"
(i.e., 1 + "2"
)?
A programming language designer could define the +
operator to do anything. Here are just some possibilities:
-
1 + "2"
results in the number3
-
1 + "2"
results in the string"12"
-
1 + "2"
raises an exception -
1 + "2"
causes the computer speakers to generate an alarm noise, then ignores the current line and skips to the next line of code -
1 + "2"
results in the boolean valuefalse
-
1 + "2"
results in the string"Today is Monday"
Which of these outcomes are reasonable? Which would result in a "simple" programming language?
f is not reasonable: there is no clear rule that would explain why adding a number to a string should result in the day of the week. It makes no sense.
Similar reasoning excludes e: it is excessively complex to introduce a third type (i.e., boolean) to explain what should happen when combining a number with a string.
The behavior of d is also eccentric: the consequences of skipping lines could be unpredictable for a developer. Furthermore, there isn’t a simple relationship between the idea that combining numbers with strings should result in speakers playing sounds. [1]
The remaining options a, b and c are more reasonable.
Both a and b would suggest a general language principle that mismatched types must be automatically converted before performing the operation. In a, numbers are preferred (i.e., strings get converted to match the number). In b, strings are preferred (i.e., numbers are converted to match the string type).
Possibility c, would make sense in a language with a general principle that mismatched types are a problem: they suggest programmer error and should result in an error or exception.
Language designers do not choose between these possibilities in isolation. The rules of the language need to be consistent with other combinations of parameters. For example, what happens if you add a number 1
to a boolean true
?
In a language based on possibility c, it is simple to anticipate that the result would also be an exception. However, for languages a and b, it isn’t straightforward: which types are preferred and how do you convert between them?
The Python programming language closely follows option c, whereas JavaScript follows option b.
+ in Python
The following table shows the different outcomes of +
in Python. You can read the table by using the row and column headers: the value of each cell is the result of adding its row header to its column header.
+ |
True |
False |
1 |
"2" |
None |
{} |
[] |
---|---|---|---|---|---|---|---|
True |
|
|
|
TypeError |
TypeError |
TypeError |
TypeError |
False |
|
|
|
TypeError |
TypeError |
TypeError |
TypeError |
1 |
|
|
|
TypeError |
TypeError |
TypeError |
TypeError |
"2" |
TypeError |
TypeError |
TypeError |
|
TypeError |
TypeError |
TypeError |
None |
TypeError |
TypeError |
TypeError |
TypeError |
TypeError |
TypeError |
TypeError |
{} |
TypeError |
TypeError |
TypeError |
TypeError |
TypeError |
TypeError |
TypeError |
[] |
TypeError |
TypeError |
TypeError |
TypeError |
TypeError |
TypeError |
|
It is apparent, by visual inspection, that the table has a simple structure. There is a parsimonious explanation that can explain the entire table:
-
Numbers are added
-
Strings are concatentated
-
Arrays (
[]
) are concatenated -
Treat
True
as the number1
andFalse
as the number0
-
Any other combination results in an error
+ in JavaScript
Consider the table of outcomes for +
in the JavaScript programming language:
+ |
true |
false |
1 |
"2" |
null |
{} |
[] |
---|---|---|---|---|---|---|---|
true |
|
|
|
|
|
|
|
false |
|
|
|
|
|
|
|
1 |
|
|
|
|
|
|
|
"2" |
|
|
|
|
|
|
|
null |
|
|
|
|
|
|
|
{} |
|
|
|
|
|
|
|
[] |
|
|
|
|
|
|
|
I have known JavaScript for decades, yet I’m still astonished by the complexity of this table.
You can verify the table by testing combinations in Node.js:
$ node
Welcome to Node.js
Type ".help" for more information.
> true + true
2
> {} + {}
'[object Object][object Object]'
> typeof ({} + {})
'string'
>
To understand the table, it helps to understand JavaScript’s history. The original purpose of JavaScript was to be a “little” programming language. It was a simple language designed for beginner programmers to add short scripts to their web pages. JavaScript’s designers expected experienced developers to use the Java programming language for sophisticated programs.
For a developer to understand the rules for +
in a language like Python, they would need an understanding of several advanced concepts: types, the differences between numbers and strings, exceptions, and how to handle exceptions. The designer of Python expects developers to understand these concepts, so he prefers that developers discover their mistakes early, rather than allowing potentially incorrect code to run in production.
In contrast, the design of JavaScript prioritizes code that runs rather than code that is correct. JavaScript makes it easier for a beginning programmer to edit and experiment with their code to seeing how their changes influence the outputs. JavaScript avoids complex compile errors or exceptions that would stop a beginner programmer from making any progress with their program.
So, JavaScript is designed with the philosophy that some result — any result — is better than a difficult-to-understand error message.
With that goal in mind, JavaScript is not entirely arbitrary. It has consistent rules underpinning the +
operator. These rules ensure that any possible combination of inputs produces some result:
[2]
-
First, any objects or arrays are converted into a "primitive" value (i.e.,
undefined
,null
, boolean, number or string). In practice, this means that thetoString()
method is used to convert the objects and arrays into strings.-
The default
toString()
method for an object ({}
) returns"[object Object]"
-
The default
toString()
method for a list ([]
,[1,2,3]
,["a","b","c"]
) converts the elements into strings (""
,"1,2,3"
,"a,b,c"
)
-
-
Next, if either operand is a string, then perform string concatenation
-
Otherwise, perform numeric addition
-
true
is equivalent to 1 -
false
andnull
are equivalent to 0
-
Perhaps one way to think of these rules is to understand that JavaScript always attempts to perform either numeric addition or string concatenation. If the operands aren’t already numbers or strings, it will first convert the values to primitives before adding.
Discussion
The question “Is JavaScript a simple language?” is not straightforward. In many respects, the language appears complex and inconsistent (recall the complexity of the outcomes of the +
operator in JavaScript). For an experienced developer, languages such as Python appear to be simpler and more consistent than JavaScript.
Nevertheless, I am hesitant to say that JavaScript is not a “good” language. Although JavaScript’s rules are not as simple as Python, there are consistent rules underpinning the language. Remembering JavaScript’s history makes it easier to understand: JavaScript is designed so that almost every operation will succeed with an outcome, even if that ‘successful’ outcome does not make sense.
Today, millions of professional developers use JavaScript. It has evolved far beyond its original purpose. As a professional developer, you have the power to make JavaScript a better and simpler language by avoiding rules that introduce unexpected complexity. You could use true + true + true + null
in your code to represent the number 3
, but that would make your code nearly impossible for others to understand. Instead, if you only use +
to add numbers (2 + 2
) or to concatenate strings ("hello " + "world"
), then you are ensuring that the programming language you use is a simple, clear and more beautiful subset of JavaScript. It is your responsibility to use discretion in the features of JavaScript (or any programming language) that you use.
The JavaScript interpreter (e.g., Node.js) is also a computer program. Thus, a discussion of simplicity in programming languages is also worth considering as a case study in software design. It is not always easy to create something simple: difficult problems often require difficult solutions.
In JavaScript, simplicity means allowing beginners to use a language with fewer concepts but more complex rules (i.e., no need to understand exceptions, but a more complex rule for +
). In Python, simplicity means giving programmers more advanced concepts, but fewer rules to remember (i.e., the +
operator raises an exception for any inputs that don’t make sense).
As you design software, carefully consider every function you write. For example, is it simpler to understand a complex firstHasGreaterAverage
function or to use an average
function twice? There is no best answer in any design decision: you need to think about your preferences, your objectives and, most importantly, the other developers on your team who need to read your code.