Hello! Since you are here, you are probably wondering why the study of programming languages a worthy pursuit. “What’s in it for me?” you may be asking. Fear not—there are benefits. “How should I study them?” you may be asking. There are good answers to this question, too.
Why Study Programming Languages?
Because benefits.
Professional Benefits
Studying programming languages will help you:
Express yourself better. Expertly wielding language, whether natural or programming, allows you to communicate ideas powerfully. Programming is a form of written expression just like writing novels, short stories, technical manuals, wikis, screenplays, essays, dialogues, articles, and poetry. Just as knowledge of rhetoric can improve your prose, verse, and arguments, knowledge of programming languages improves your ability to express yourself in code. Studying the mathematical formalisms underlying language syntax and semantics leads to expertise in reading and writing precise specifications.
Become a better technical decision-maker. You will be able to choose the most appropriate language for a given task. Some languages do a great job expressing some kinds of tasks and do a terrible job at others. You will be able to describe design tradeoffs (garbage collection or not, compile-time type safety or not) and be able to articulate how exactly languages like Rust and Swift compete against C and C++.
Learn new languages more easily. Thinking in terms of language independent concepts (e.g. types, sequencing, iteration, selection, recursion, concurrency, subroutines, parameterization, naming, scope, abstraction, inheritance, composition, binding, etc.), rather than in one particular language’s syntactic constructs, enables you to adapt to any programming environment.
... Mastering more than one language is often a watershed in the career of a professional programmer. Once a programmer realizes that programming principles transcend the syntax of any specific language, the doors swing open to knowledge that truly makes a difference in quality and productivity. — Steve McConnell
Become more productive, as you become able to use the languages you do use more effectively. If you know how and why a language was designed, you can:
choose the best and most efficient feature for the task at hand
utilize some of its non-obvious powerful features
simulate useful (and powerful) features from other languages that your language lacks
understand obscure features
understand weird error messages
understand and diagnose unexpected behavior
understand the performance implications of doing things a certain way
use a debugger effectively
Design (and implement!) your own language. There are many good reasons for creating new languages to solve problems (see this essay by Tom Van Cutsem). Even if you never create a complete language, you might need to write your own little language as part of a larger application, for example:
A query language for database access
A query language for a search engine
A calculator
A console interface to an adventure game
Expand your mind. You cannot grow when your thinking about programming is fixed. Study programming languages to encounter fascinating ways of programming you might never have even imagined before, achieving enlightenment as you deeply understand pattern matching, type inference, closures, prototypes, introspection, instrumentation, just-in-time compilation, annotations, decorators, memoization, traits, streams, monads, actors, mailboxes, comprehensions, continuations, wildcards, regular expressions, proxies, and transactional memory.
Exercise: What is a monad? What is a continuation?
Your study will lead you to embrace languages, and programming environments, that may be different than you’ve ever imagined. You do know about eToys, right?
Prepare yourself to build some stunning things. Imagine formally-verified compilers, formally-verified hardware, formally-verified programs, the world’s smartest and fastest IDE, code generators from specifications, block-programming environments that actually work, and so on. To build programs that manipulate and build other programs, you need the deep understanding of what programs are, and this comes from studying the field of Programming Languages.
So basically, you will become happier and make more money.
Social Benefits
Studying programming languages may even enable you to:
Understand and participate in exciting academic and professional discussions, both at meetups and online.
Socialize with researchers in linguistics, programming languages, and people interested in communication.
Gain a little fame, and perhaps make a difference in the world, with a programming language you design yourself.
Impress your friends with esoteric, geeky, fun, and sometimes arcane knowledge.
I write world-changing applications in languages you have probably not yet heard of. My code is poetry, meanwhile yours is oh-noetry.
Algorithmic Analysis • Algorithmic Strategies • Data Structures and Algorithms • Automata • Computability • Computational Complexity
Architecture and Organization
AR
Digital Logic and Digital Systems • Machine-level Data Representation • Assembly-Level Machine Organization • Memory System Organization and Architecture • Interfacing and Communication • Functional Organization • Multiprocessing and Alternative Architectures • Performance Enhancements
Computational Science
CN
Modeling and Simulation • Processing • Interactive Visualization • Data, Information and Knowledge • Numeric Analysis • Symbolic Computation • Mathematical Modeling • High-Performance Computing
Discrete Structures
DS
Logic • Sets, Relations, and Functions • Proof Techniques • Basics of Counting • Graphs and Trees • Discrete Probability
Graphics and Visualization
GV
Media Applications • Digitization • Color Models • Rendering • Modeling • Animation • Visualization
Human-Computer Interaction
HCI
Interactive Systems • Testing • Collaboration and Communication • Statistical Methods • Human Factors • Mixed, Augmented, and Virtual Reality
Information Assurance and Security
IAS
Defensive Programming • Threats and Attacks • Network Security • Cryptography • Web Security • Platform Security • Policy and Governance • Digital Forensics • Secure Software Engineering
Information Management
IM
Database Systems • Data Modeling • Indexing • Key-Value, Document, Relational, and Graph Databases • Query Languages • Transaction Processing • Distributed Databases • Physical Database Design • Data Mining • Information Storage and Retrieval • Multimedia Systems
Intelligent Systems
IS
Knowledge Representation and Reasoning • Search • Machine Learning • Reasoning Under Uncertainty • Agents • Natural Language Processing • Robotics • Speech Recognition and Synthesis • Perception and Computer Vision
Networking and Communication
NC
Networked Applications • Reliable Data Delivery • Routing and Forwarding • Local and Wide Area Networks • Resource Allocation • Mobility • Social Networking
Operating Systems
OS
Operating System Organization • Concurrency • Scheduling and Dispatch • Memory Management • Security and Protection • Virtual Machines • Device Management • File Systems • Realtime and Embedded Systems • Fault Tolerance • System Performance and Evaluation
Platform-Based Development
PBD
Web Platforms • Mobile Platforms • Industrial Platforms • Game Platforms
Parallel and Distributed Computing
PD
Parallel Decomposition • Communication and Coordination • Parallel Algorithms, Analysis, and Programming • Parallel Architecture • Parallel Performance • Distributed Systems • Cloud Computing • Formal Models and Semantics
Programming Languages
PL
Object Oriented Programming • Functional Programming • Event-Driven and Reactive Programming • Type Systems • Program Representation • Language Translation and Execution • Syntax Analysis • Semantic Analysis • Code Generation • Runtime Systems • Static Analysis • Concurrency and Parallelism • Type Systems • Formal Semantics • Language Pragmatics • Logic Programming
Software Development Fundamentals
SDF
Algorithms and Design • Fundamental Programming Concepts • Fundamental Data Structures • Development Methods
Computational Paradigms • Cross-Layer Communications • State and State Machines • Parallelism • Evaluation • Resource Allocation and Scheduling • Proximity • Virtualization and Isolation • Reliability through Redundancy • Quantitative Evaluation
Social Issues and Professional Practice
SP
Social Context • Analytic Tools • Professional Ethics • Intellectual Property • Privacy and Civil Liberties • Professional Communication • Sustainability • History • Economics of Computing • Security Policies, Law, and Crime
You might even find the study of languages, especially in its theoretical side, to be even more fundamental than the favorite of job interviewers—data structures and algorithms. Language theory helps us organize our thoughts around how information is both expressed and manipulated. Without a way to represent data or computation, we really wouldn’t have computer science.
Did you read the article? Okay, let’s lay out a study plan.
An effective study of programming languages has three parts:
Learn Languages
Learn a bunch of existing languages, and understand how each expresses and implements various concepts
Learn Concepts
Study many concepts, and understand how each is expressed in a variety of languages
Learn Formalisms
Learn and be able to apply mathematical precision to syntactic and semantic descriptions.
The study of those three aspects should be carried out in the proper contexts:
History
Understanding history is essential for learning any discipline and the study of programming languages is no exception.
Pragmatics
Programs and Programming Languages are written by people for people. Understanding the human use and perception of languages is crucial.
Let’s give a very brief overview of these five items.
History
Your study of programming languages is enhanced when done in a historical context. Programming Languages do have a rich history. There have been many books and articles written on the History of Programming Languages (HOPL). It’s really fascinating! And super important, too.
We can’t boil the history of the field into a couple paragraphs, but there are some historical highlights worth mentioning to whet your appetite:
Early History
Prior to the 1940s we have the Jacquard machine and Ada Lovelace's programming of the Analytical Engine. Plankalkül is designed in the early 1940s but never gets popular. It’s a high-level language, yes, but mostly symbolic.
1950s-1960s Rise of natural language-like languages
Grace Hopper leads the COBOL effort (business computing), John McCarthy leads Lisp (AI), John Backus leads Fortran (scientific computing). Early Algol becomes popular and eventually we get behemoth multi-purpose languages like Algol 68 and PL/1.
1970s The structured programming revolution begins
Languages providing information hiding, such as Modula, CLU, Simula, Mesa, and Euclid attempt to address the “software crisis.” Structured and modular features are bolted on to earlier languages.
1980s Object-orientation takes over the world
Though begun in the 1960s with Simula and refined in the 1970s at Xerox PARC with Smalltalk, OO—or approximations to it—explodes in the 1980s with C with Classes (since renamed C++), Objective-C, Eiffel, and Self. Earlier languages such as Lisp and Pascal gain OO features, becoming Common Lisp and Object Pascal, respectively.
1990s The World Wide Web appears
Perl becomes popular. Java, JavaScript, and PHP are created with web applications in mind.
2000s: Interest in established dynamic languages such as Ruby takes off
Scala shows that static languages can feel dynamic. Clojure arrives as a dynamic, modern Lisp, leveraging Java’s virtual machine.
2010s: Old things become new again
Multicore processors and “Big Data” revive interest in functional programming. Older languages such as Python and R, and the newer Julia language, find use in data science. Net-centric computing and performance concerns make static typing popular again as Go, Rust, and Swift challenge C and C++ for native applications.
Next, you’ll need to go much deeper. Here are some great reads and watches:
Many people don’t realize what incredible work happened in the 1960s and 1970s. Learn about this era in this informative and entertaining look into what this work could have turned into, so much sooner that it did, if it did:
Follow up with your own web searches for “History of Programming Languages” or “Evolution of Programming Languages.”
Learning Languages
When studying actual languages, you should learn:
Which languages are significant, either because they:
have come into widespread use, or
have made a huge impact on the design of future languages
The reason that each language was designed, since you need to know
What problems were its designers trying to solve?
How successful were they?
Understanding a language’s motivation for existence is essential. It is unfair to criticize a language for doing a bad job in an area which it was never intended to be used for.
Some significant languages
Here are a few languages that for some reason or another rate as significant:
Fortran (I, II, IV, 66, 77, 90, 95, 2003, 2008, 2015)
Here are some languages and their motivations for being created. In some cases, the language
found a niche outside the purpose for which it was designed. But the point is that some
problem was seen as significant enough to warrant a new language.
Ruby — To be the language Matz wanted (and better than Perl)
Lua — General-purpose scripting
Java — Compact downloadable executable components
C# — To be the Java of the Microsoft (.NET) world
JavaScript — Client-side scripting for web applications
PHP — Server-side scripting for web applications
Postscript — Formatting of printed documents
XSLT — Hierarchical document transformation
Clojure — To be a modern Lisp-dialect for the JVM
Dart — To be a better language for modern (web) apps
Go — To build large distributed, interconnected systems
Rust — Large, safe, network clients and servers
Hack — To do PHP-compatible things while keeping your sanity
Elm — To be a “delightful language for reliable webapps”
Julia — Scientific computing in a dynamic language with near compiled-language speed
Swift — Because it was time to make something more fun than Objective-C for iOS apps
Ballerina — To compose network services for cloud computing
Verse — Metaverse applications
Exercise: Java is an example of a language whose primary use turned out to be completely different from its original design purpose. What are Java’s niches today?
As you learn more languages, you will start to enjoy Rosetta Code,
and hopefully contribute!
Learning Language Concepts
In addition to learning a dozen or so programming languages, you will want to accumulate a good deal of organized knowledge about the important (language-independent) concepts that underlie programming languages. Such concepts include:
Modules, packages, namespaces, classes, units, etc.
Import and export
Object orientation
Separate compilation
Concurrency
Threads vs. processes
Events
Shared resources and synchronization
Scheduling
Asynchronicity: callbacks, promises, async/await
Channels vs. Processes
Buffered vs. Unbuffered communication
Metaprogramming
Introspection
Reflection
Eval
Decorators
Metaclasses
Mathematical Formalisms
A study of just about anything can be enhanced with mathematical and logical precision. For programming languages, we want a way to specify, unambiguously, exactly what are the legal programs in a language, and what exactly they mean. Natural language is open to interpretation and fuzziness and ambiguity, so we would like to bring in some mathematical machinery. Topics we use include logic, set theory, lambda calculus, automata theory, computation theory.
Math is used to assist with the definition of two aspects of language: syntax (structure) and semantics (meaning).
Syntax
A programming language gives us a way structure our thoughts. A program’s structure is determined by the syntax of the language. The syntax defines which strings of characters are valid programs, and if valid, how the characters are grouped into words (called tokens) and how the words are grouped into phrases (such as declarations, expressions, statements, etc.)
A syntax is normally defined formally and mathematically, using objects such sets, sequences, alphabets, and grammars.
Syntactic formalisms are well understood in computer science and fluency with at least one formalism is an expectation of undergraduate computer science students and most programers. Among those you will encounter in your study of programming languages are:
BNF,
EBNF,
ABNF,
plain old Context-Free Grammars,
Syntax Diagrams, and (my favorite)
Parsing Expression Grammars.
Semantics
The semantics of a language gives meaning to its programs. We need to be able to precisely and unambiguously define the meaning (or effect) of every program. This requires understanding and using sets, relations, functions, and similar constructs. Theories of functions such as the λ-Calculus are very helpful here.
Unlike syntax, for which formalisms are straightforward, mathematical approaches to semantics are generally undertaken only in graduate computer science courses.
Pragmatics does not affect the formal specification of programming languages; however, pragmatic concerns must guide your design of a programming language—if you want it to be easy to read, easy to write, and able to be implemented efficiently. Pragmatics encompasses:
The sort of applications a language best suited for
The advantages and disadvantages relative to other languages
Expressiveness and performance characteristics
Common programming idioms (good ways and not-so-good ways of doing things)
An implied execution model: Assignment-based, Stack, Combinators, Data-flow, etc.
The standard library or libraries
The ecosystem for 3rd party libraries (e.g. NPM for JavaScript, Pip for Python, Gems for Ruby, Rocks for Lua, Maven for Java, ...)
One of the more common ares of study within pragmatics is how one evaluates a language.
Criteria for Evaluation
Reasonable people know that all languages have their pros and cons. But why are some things
pros and other things cons? There are certain technical criteria:
Is the language:
Easy to read? Because:
Over 90% of programmer time is reading and modifying existing code
Labor costs dwarf hardware costs
If someone can’t understand existing code, it will get thrown away
Easy to write? Because:
If the learning curve is too high, who would bother to use it?
Highly Expressive? That would help reading and writing. Does the language have:
Operators like A = B + C which adds whole arrays in Fortran 90?
Abstract data types (with encapsulation)?
Module and package structures to aid programming-in-the-large?
A rich operator set, as in languages like APL and Perl?
Rich type/object structures supporting inheritance, composition and aggregation?
Polymorphism, overloading, aliasing?
Higher order functions?
Pattern matching?
Built-in control flow (e.g. unification, backtracking)?
Facilities for symbolic computation?
Support for asynchronous, concurrent, and distributed programming?
Designed to make it impossible to making (some) stupid mistakes? Some languages do! For example you might see:
A requirement to declare variables and types detects spelling errors early
Simple iteration over collections vs. crazy-flexible for loops
Robust switches with pattern-match exhaustiveness vs. the abomination which is the C switch
Safe discriminated sum types vs. unsafe C unions
Languages without insecure features, such as user-specified pointer deallocation, pointer arithmetic, automatic coercions
Whitespace significance prevents the classic Fortran 77 example
DO 10 I = 1 . 5
PRINT * "Hello"
10 CONTINUE
Designed to allow quick and incremental compilation? Because breaking a programmer’s flow by constantly hitting that compile button or having to run linkers, or such things get annoying.
If compilation is as fast as you can type, development time and cost can be greatly reduced.
If compilation can be incremental, there’s no need to recompile after making small changes.
If compilation can be quick, some compilation can be done at runtime to adapt to changing conditions.
Amenable to efficient target code generation?
Languages with static scope and type checking keep the runtime system free from tons of work, e.g. no runtime variable lookup: all variable references have already been resolved to machine addresses.
Languages with no first-class functions allow for stack-allocation of frames.
There are non-technical, subjective criteria, too. Good and successful are not the same! Success comes from:
Being the only language suitable for a specific problem
Personal preference (no accounting for taste)
Good development environments
Fast compilers (not necessarily a language issue!)
A massive ecosystem (like NPM or Maven or Pip or Gems)
Patronage (support from a government or big company)
“Everyone else is using it”
“The boss made me use it”
Economics and Inertia — “we already invested too much in this to change”
Laziness — “I’m too tired to learn a new language”
Exercise: What else can you think of that makes people want to use a particular language?
Expressiveness
Expressiveness is a pragmatic concern, too, influencing how a language can be wielded to say what needs to be said. Some languages want to be verbose, low-level, or “close to the machine” and others want to be terse or expressive. Here’s the way different languages allow you to express a function to return the sum of the squares of even numbers in a list. In class, we’ll discuss the pragmatic issues that arise in each:
int sum_of_even_squares(int* a, unsigned int length) {
int total = 0;
for (unsigned int i = 0; i < length; i++) {
if (a[i] % 2 == 0) {
total += a[i] * a[i];
}
}
return total;
}
func sumOfEvenSquares(a []int) int {
total := 0
for _, x := range a {
if x % 2 == 0 {
total += x * x
}
}
return total
}
function sumOfEvenSquares(a) {
return a.filter(x => x % 2 === 0).map(x => x**2).reduce((x, y) => x + y, 0)
}
public static int sumOfEvenSquares(int[] a) {
return IntStream.of(a).filter(x -> x%2==0).map(x -> x*x).sum();
}
def sum_of_even_squares(a):
return sum(x*x for x in a if x % 2 == 0)
sumofevensquares: {+/x[&~x!2]^2}
Related to expressiveness is the idea of code as art, or art as code. Here’s a discussion of what this actually means:
Popularity
There are countless numbers of ways to measure popularity! You really can’t make popularity statements without stating your measure. Then people will argue whether your measure is even valid. 🤷♀️ Nevertheless, it’s fun to look at this stuff from time to time.
RedMonk has a language ranking scheme that combines pull requests on GitHub and questions on StackOverflow. (One could argue that this measures how confusing a language is too...maybe most of the StackOverflow questions are language complaints?) Here is the ranking from June 2021:
RedMonk gives these rankings:
JavaScript
Python
Java
PHP
CSS
C++#
C#
TypeScript
Ruby
C
Swift
R
Objective-C
Shell
Scala
Go
PowerShell
Kotlin
Rust
Dart
Another ranking system, by Tiobe, ends up with a radically different top 20. They say: “The ratings are based on the number of skilled engineers world-wide, courses and third party vendors. Popular search engines such as Google, Bing, Yahoo!, Wikipedia, Amazon, YouTube and Baidu are used to calculate the ratings.”
Here’s a current take:
But what makes a language popular? MPJ has thoughts:
Understanding Evaluation Tradeoffs
You can’t have everything, it seems:
The expressive power of dynamic typing, polymorphic type systems, functions as first-class values, higher-order functions, and closures can sometimes impact performance.
Automatic garbage collection saves billions of dollars in programmer time, but isn’t always a good idea in embedded, life-critical, real-time systems.
A language may be wonderful and amazing and increase developer productivity, but if no talented people are out there that know the language, how will you hire the best team?
Languages that are intentionally designed to be horrible (Brainfuck, Java2K, Malbolge, etc.) have some intellectual and educational value (and offer amusement).
Exercise: Think up some other tradeoffs.
No Competition!
Don’t make the mistake of thinking programming languages are like sports teams—they don’t compete against each other. Don’t root for one to “win.”
Realize that languages serve different purposes and are often used together to build systems.
Summary
We’ve covered:
The professional benefits to the study of programming languages
The social benefits to the study of programming languages
Three knowledge areas: languages, concepts, formalisms