5 Programming Languages in 10 Minutes-ish
You don’t need to be an expert in every language, but understanding the fundamentals can certainly help
Learning about different languages — even if you’re not planning on actively using them — is a great practice for any developer. This is because if you spend most of your time focused on one technology, you’ll miss out on any potential improvement you could be finding somewhere else.
Don’t get me wrong: This isn’t about mastering every language — that’s neither practical nor possible. But who’s to say you haven’t been missing out on some functional-programming practices because you’ve only focused on object-oriented alternatives until now?
So in the spirit of one of my favorite programming books of all time, I’ve picked five programming languages for you to review.
And even if they don’t apply to your day to day, check them out and look at what they can do. They might surprise you.
JavaScript
I picked JavaScript as the first one because not everyone works in the web sector. And even inside it, not every backend developer works with Node.
JavaScript is a very popular language right now since over time its community has created different interpreters and runtimes for multiple architectures. So while it’s mostly known for its role in the web-development industry, JavaScript can also be used in:
- AI and machine learning, thanks to projects such as TensorFlow.js.
- Tooling, thanks to headless runtimes such as Node.js and Deno.
- Mobile apps, given the number of frameworks available (React, React Native, Ionic, others).
- Game development, thanks to so many engines and libraries being available.
- IoT solutions: Thanks to frameworks such as Mongoose OS, you can use JavaScript to program microcontrollers.
Main characteristics of JavaScript
Some of the most appealing and interesting characteristics of JavaScript are:
Dynamic typing
JavaScript supports dynamic types, which means you can change the content of a variable during runtime without caring for the type of the data.
Look at the example above — clearly that’s not the way to take advantage of dynamic types, but it’s definitely possible thanks to JavaScript’ flexibility.
The main benefits of a dynamic-typing system are:
- You get a simpler language because you don’t have to worry too much about type declarations and definitions.
- Development times diminish, given there’s less complexity involved around writing the code.
- Your code is potentially easier to read (assuming you’re also following other best practices) because there’s less boilerplate and repeated code.
Multiparadigm support
Unlike other languages, where they focus on a single programming paradigm, JavaScript tries to mix the best of the object-oriented world with the functional world.
The language supports classes, methods, method overrides, and public and private members (at least, it’ll soon support private members).
And at the same time, JavaScript has the concept of functions as first-class citizens. This, in turn, means you can combine functions and write more declarative code, currying functions and other FP patterns (such as reduce
, map
, and others).
Async I/O
Asynchronous code is native in JavaScript, which means the language itself provides you with some incredible constructs you can use to write nonblocking I/O operations without having to worry about threads or other complex mechanics. Of course, we’re talking about callback functions, promises, and async
/await
.
Example taken from Mozilla’s developer site
Notice the above code — it reads easily and you don’t have to mentally keep track of who’s making a request or when the response is received. The async
/await
combo allows you to write asynchronous code just as if it was synchronous code.
Very performant for an interpreted language
One misconception about JavaScript is that because it’s not compiled it can’t be very performant. However, most JS runtimes implement a just-in-time (JIT) compiler, which makes its execution very fast. In fact, the longer the code runs, the more chances there are for the JIT compiler to optimize that execution.
Io
Io is a pure object-oriented language, taking some cues from others such as SmallTalk, Lisp, and Lua. It, like JavaScript (and others), has a prototype-based object model and has no interest in making a distinction between classes and objects. Everything is an object in Io.
It runs on a small virtual machine, which makes it perfect for embeddable use cases. Io used to be used by Pixar for their RenderMan rendering pipeline — before they made the switch to Python. And while the author has been cited saying it was rumored to be used as part of a satellite, I personally couldn’t find any evidence of it.
That being said, Io’s model is very interesting since it’s based on messages and objects. As I said, everything in Io is an object, and messages are just the way you interact with them. Let’s take a look at a basic “Hello, World!” example:
"hello there peeps!" print
It needs a bit of getting used to, especially if you’re coming from other C-like languages, but in this example, we’re passing the print
message to our string object.
Let’s look at another example, one dealing with lists of numbers:
d := List clone append(30, 10, 5, 20)
d print
This is an interesting one because it shows the mechanics of message passing. The above can be read like this:
d := (List.clone()).append(30, 10, 5, 20)
d.print()
The first thing to happen is that we clone the list, which is how you create new objects in Io. Then, you call the append
method on the new instance. How would you also add a call to the sort
method so the end result has the numbers correctly ordered?
Like this:
d := List clone append(30, 10, 5, 20) sort
d print
What’s so special about Io then?
Leaving aside the peculiar syntax, Io has some very interesting characteristics:
- It’s purely object-oriented, using prototypes instead of classes. This means for you to create a new object, all you have to do is clone an existing one. This simplifies the model quite heavily.
- It has higher-order functions. This allows for a functional approach to programming. It’s very similar to how JavaScript approaches this subject as well. This essentially allows you to use functions, or slots as they’re known in Io as attributes, allowing you to compose and pass behavior around. This is one of the many features that allow for the huge flexibility of this language.
- Introspection and reflection. In Io, you can get the actual code of any method and modify it during execution. This, in turn, allows for what’s commonly known as metaprogramming, or the ability to change the language itself using your code.
There are others, like lazy evaluation, incremental garbage collection, exception handling, and more. Io is a perfectly functional language, and if you want to know more about it, you can check out their documentation.
As a closing thought about this interesting specimen, I wanted to give you a glimpse at its metaprogramming powers. Here we’re going to be redefining the +
operand. That’s something so basic that very few languages allow for that.
originPlus := Number getSlot("+")
Number + := method(i, if( i < 10, self originPlus(i), "that's just crazy!"))
(1+2) println
(2 + 100) println
The above code is:
- Getting a reference to the
+
method of theNumber
object. - Overriding the
+
method of theNumber
object with a dynamic method that receives a variablei
and returns anif
statement. Theif
statement is checking the value ofi
and deciding what to do (and, hence, return) based on it. - The last two lines are just testing our changes.
Of course, the output from that script is:
3
that's just crazy!
You can play around with the code and more Io on this live REPL.
Prolog
Prolog is one of my favorite “unused but potentially very useful someday” languages. The paradigm behind it is just so full of potential that I’m perplexed that it’s not more popular. I’ve written a full article on it in the past, but the gist behind it is the following:
Prolog is a logic programming language. It really goes very much against most of our experience in the IT industry, where we know what we need to do, so we tell the computer what to do — and in some situations, even how to do it.
Prolog is different, though. With logic programming, you set up a universe and explain to the computer what your reality looks like. Then, you ask questions to it. The what to do and how to do it is up to the runtime.
Interesting, isn’t it?
Look at the following code for an example:
magicNumber(3)
magicNumber(5)
magicNumber(7)
magicNumber(9)
?- magicNumber(X), magicNumber(Y), plus(X, Y, 12)
The first four lines are setting up the universe — we’re telling our interpreter that we have four magic numbers. And then we’re asking a question: Given a magic number in X
and a magic number in Y
, which ones will return 12
if I add them together?
And the results are, of course:
3, 9
5, 7
7, 5
9, 3
Notice how I didn’t have to iterate over them or find multiple combinations or any other logic. I just asked a question.
What makes Prolog so cool then?
You might not agree with me, but the main and most important feature that makes Prolog so interesting and a must-try to any developer is the logic.
You might’ve studied truth tables during your formative years, and you might’ve seen a thing or two about using logic to prove math problems. But if you haven’t seen logic applied to solving programming problems, then you’ve been missing out.
Prolog uses pure and simple logic to solve a problem for you. Instead of you having to code your solution, you rely on Prolog to find one.
Picture the following scenario: You have to model a set of students, teachers, and subjects. Each student can be studying several subjects, and each teacher can impart one subject.
If you were to model this using traditional programming techniques, you might have thought of using a database and querying it for things like “which subjects is student X studying?” or “who’s studying X subject?”
With Prolog, if you model the problem using facts and rules, you can do something like:
studies(charlie, csc135).
studies(olivia, csc135).
studies(charlie, csc1000).
studies(jack, csc131).
studies(arthur, csc134).
teaches(kirke, csc135).
teaches(collins, csc131).
teaches(collins, csc171).
teaches(juniper, csc134).
professor(X, Y) :- teaches(X, C), studies(Y, C).
Note: Example taken from http://athena.ecs.csus.edu/~mei/logicp/prolog/programming-examples.html.
You have the facts stating who’s studying what and who’s teaching what subjects. Then the last line is setting up a rule, stating that a professor X
of student Y
is someone who teaches a subject that someone else studies.
Then, you’re done. This is your new database, and you can ask questions (or, rather, query it) like this:
studies(What, csc134). //who's studying 'csc135'
\=> What=arthur
professor(kirke, What). //who's professor Kirke teaching?
\=> What=charlie
\=> What=olivia
As you can see, the logic engine takes care of the heavy part, your job basically consists of correctly setting up the universe so you can then ask it the right questions.
This is to me the wonder of Prolog and why it’s used on AI for projects such as IBM’s Watson or for code-generation tools such as GeneXus.
You can try Prolog online using this REPL.
Clojure
If you want to talk about strange-looking languages, Clojure is definitely up there (without running into esoteric languages, that is).
Clojure is a mostly functional programming language, which offers a great level of dynamic behavior, coupled with fantastic support for multithreading.
The interesting thing about it, and one of the main reasons why it looks so different from other C-like languages, is that because it’s based on Lisp, everything is a list.
(def a "hello world")
(print a)
That code is going to output hello world
, but the interesting fact about it is that those are just two lists. Everything between two parentheses in Clojure is a list. And because of that, the first item of the list is always treated as a function, which means that if you want to actually declare a list of values, you need to call the list
function:
(list 1 2 3 4 5 6)
'(1 2 3 4 5)
The second line is a shorthand notation to avoid the use of the obvious list
function.
Another interesting thing to know about Clojure is that unlike its parent Lisp, this one runs on top of the JVM, which means you get access to Java. allowing you to mix Java types, classes, and other features of the JVM in your Clojure code. This means you can do something like this:
(new java.util.Date "2016/2/19")
You’ll actually get a valid Java date object to work with.
Main features
- Dynamic environment. Yet again, Clojure is another dynamic language that allows you to redefine its behavior during runtime. This turns into a very powerful minimalist JVM language. It mixes the power of the JVM with an unknown level of flexibility for Java developers.
- Easier concurrency. Because the main data structures in Clojure are immutable, sharing them between threads is easier — you don’t have to worry about unwanted modifications. With Clojure, you can forget about managing locks between threads.
- Functional programming. While Clojure isn’t entirely pure, when it comes to FP, it heavily favors functional programming. Just like other languages here, it has the concept of functions as first-class citizens, which means you can pass them around and combine them to create new, more complex behavior.
- It’s like Java but better. Because it runs on top of the JVM and you have access to all of its features, you can literately do anything you’d be able to do with Java, even using some of its own native types and classes. This makes it a great target for Java developers who are looking to make the jump into a more interesting language.
Clojure is actively being used by hundreds of companies, of all sizes and industries.
- Walmart. They used Clojure to create a store-management system that can handle over 5,000 stores. They talk about it here.
- Atlassian. They used Clojure to build real-time collaboration features. They have a nice talk where they cover this and the results they got here.
- ThoughtWorks. They’re using Clojure to reduce development times and to accelerate an existing Java team. This one is a clear statement to one of the main benefits of this language. Thanks to the fact that it works on top of JVM, having your JAVA team switch to Clojure means you can avoid changing the underlying technology for your project.
And the list goes on and on — you can check all success cases here.
Rust
Finally, Rust is our last stop throughout this journey. I wanted to cover this one because it shows some very distinct differences from the others.
Rust was designed and developed inside the Mozilla Research program, with performance and safety in mind. Syntactically, it looks a lot like C++, and like it, it needs to be compiled before executed.
However, even when visually it might resemble C or C++ a lot, it also takes a lot of cues from other languages, making it unique and powerful. For instance:
- Almost everything in Rust is an expression. This means you can create blocks with curly bracers and then assign their content to a variable, like this:
fn main() {
let x = 42;
let y = {
let x\_squared = x \* x;
let x\_cube = x\_squared \* x;
x\_cube + x\_squared + x //this is the return value of the expression
};
println!("y is {:?}", y);
}
- Like with Lisp (and Clojure), functions don’t need to use the
return
statement. The last line of the function will define its return value. - Pattern matching. Through the use of the
match
keyword, you can define different patterns for your variables and match accordingly. The following code will define a range you can use to match the value of a variable and default to another behavior in case there’s no match:
let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
What makes Rust so cool?
- Static typing versus dynamic typing. Rust tries to play nice with both worlds on this one. Instead of forcing the developer to write and define types for everything, it only asks for top-level variables (like function arguments and constants) to declare their types, and then inside functions, type inference is allowed. Look at the following example, Rust can infer the type of
result
because it knows the types ofa
andb
as well as the return type of the function.
fn simple_adder(a: i32, b: i32) -> i32 {
let result = a + b;
result
}
- No interfaces nor classes — but an alternative. Instead of providing the classic class+interface combo, Rust’s bet is on traits, implementations, and structs. You can define a struct with the fields of your data structure, then create a specific implementation around it by adding behavior to it (e.g., methods). And finally, you can even create traits, which act like abstract classes or interfaces, which declare a set of methods that you’ll then have to implement.
The above working example shows how you can use these new constructs to solve your object-oriented needs. You can try the above example using an online REPL here.
- A lack of a garbage collector actually helps. Rust has no garbage collector. Instead, it allows you to decide where to store data (either the stack or the heap), and it then decides when that memory is no longer needed. This, in turn, provides a more efficient and performant memory utilization.
- Ideal for embedded systems. Because of its low-level API and the direct access to hardware and memory, Rust is the ideal choice when having to work on embedded systems with a modern language.
If you’re coming from the statically typed world, or just working closer to the hardware than other sectors of our industry, you might want to give Rust a chance.
Conclusion
And that’s it for our quick overview of these languages. Of course, there are so many others out there that I know I’ve probably left your favorite off the list. However, the point of this was to cover different implementations of different programming paradigms, showing you how versatile languages can be.
If I managed to pique your interest in at least one of these options, then I’ll consider my job as done.