Hello • Manahuu • Olá • Hallo • こんにちは • Sawubona • 안녕하세요 • नमस्ते • Bonjour • مرحبًا • Merhaba • Aloha • Cześć • Mabuhay • سلام • Aaniin • Привет • ᎣᏏᏲ • 你好 • Dia duit!
Do you find it interesting that humans have so many different ways to express themselves? Even a single thought can be communicated in a multitude of ways.
This holds not only for human greetings but also for computations.
How many ways can you express the computation of summing the squares of the even numbers in a list? Let’s look at a few ways. One approach is to explicitly iterate through the list, checking each element to see if it is even, and if so, squaring it and accumulating the square into the final answer. This is the approach we need to take in Go:
func sumOfEvenSquares(a []int) int { total := 0 for _, x := range a { if x % 2 == 0 { total += x * x } } return total }
And in Lua:
function sumofevensquares(a) local total = 0 for _, x in ipairs(a) do if x % 2 == 0 then total = total + x * x end end return total end
And in Zig, it’s the most idiomatic way:
fn sumOfEvenSquares(a: []const i32) i32 { var total: i32 = 0; for (a) |x| { if (@mod(x, 2) == 0) { total += x * x; } } return total; }
Another approach is to first filter (a.k.a. ”select”) the even numbers, then map the squaring operation over each of them, and finally reduce (a.k.a. “fold”) the plus operation over the filtered-and-squared values to produce the result. For example:
[3, -2, 9, 51, 20, 8, 0, -31, 10][-2, 20, 8, 0, 10][4, 400, 64, 0, 100]568Most major programming languages have these operations built-in.
function sumOfEvenSquares(a) { return a.filter(x => x % 2 === 0).map(x => x**2).reduce((x, y) => x + y, 0) }
function sumOfEvenSquares(a: number[]): number { return a.filter(x => x % 2 === 0).map(x => x ** 2).reduce((x, y) => x + y, 0) }
def sum_of_even_squares(a) a.select{|x| x % 2 == 0}.map{|x| x * x}.reduce(0, :+) end
func sumOfEvenSquares(_ a: [Int]) -> Int { return a.filter{$0 % 2 == 0}.map{$0 * $0}.reduce(0, +) }
(defn sum-of-even-squares [a] (->> a (filter even?) (map #(* % %)) (reduce +)))
int sumOfEvenSquares(int[] a) { return IntStream.of(a).filter(x -> x % 2 == 0).map(x -> x * x).sum(); }
int SumOfEvenSquares(int[] a) { return a.Where(x => x % 2 == 0).Select(x => x * x).Sum(); }
fun sumOfEvenSquares(a: Array<Int>): Int { return a.filter { it % 2 == 0 }.map { it * it }.sum() }
def sumOfEvenSquares(a: Seq[Int]): Int = a.collect { case x if x % 2 == 0 => x * x }.sum
function sumOfEvenSquares(a) sum(x -> x^2, filter(x -> x % 2 == 0, a), init = 0) end
sumOfEvenSquares := method(a, a select(isEven) map(x, x**2) reduce(x, y, x + y) ifNil(return 0))
fn sum_of_even_squares(a: &[i32]) -> i32 { a.iter() .filter(|&&x| x % 2 == 0) .map(|&x| x * x) .sum() }
sub sum_of_even_squares { sum0 map { $_**2 } grep { $_ % 2 == 0 } @_; }
let sum_of_even_squares a = a |> List.filter (fun x -> x % 2 = 0) |> List.sumBy (fun x -> x * x)
Sometimes you can specify the filtering, mapping, and reducing functions without parameters:
sumOfEvenSquares :: [Int] -> Int sumOfEvenSquares = sum . map (^ 2) . filter even
: sum-of-even-squares ( seq -- n ) [ even? ] filter [ sq ] map sum ;
Sometimes the filtering and mapping can be expressed in a single construct known as a comprehension (which makes a new list at each step), or as a generator expression (which generates the list elements lazily):
def sum_of_even_squares(a): return sum(x**2 for x in a if x % 2 == 0)
def sumOfEvenSquares(a: Seq[Int]): Int = (for x <- a if x % 2 == 0 yield x * x).sum
proc sum_of_even_squares(a) { return + reduce ([x in a] if x % 2 == 0 then x*x else 0); }
function Sum_Of_Even_Squares (A : Integer_Array) return Integer is begin return [for X of A when X mod 2 = 0 => X * X]'Reduce ("+", 0); end Sum_Of_Even_Squares;
Some languages have really, really powerful operators for processing arrays:
sumOfEvenSquares ← {+/((2|⍵)=0)×⍵*2}
sumofevensquares: {+/x[&~x!2]^2}
function sum_of_even_squares(a) result(total) integer, intent(in) :: a(:) integer :: total total = sum((a**2) * merge(1, 0, mod(a, 2) == 0)) end function
function sumOfEvenSquares(a) sum((a[a .% 2 .== 0]).^2) end
The examples above all express the operation using high-level programming languages. If you were working at a very low-level, say assembly language, the procedure might be a little harder to figure out. The parameters have to come from somewhere. And at a low-level, what even is an array? It’s actually a contiguous block of memory identified by two things: (1) the address of the block and (2) the number of elements! Here’s the function using ARM 64 Assembly, where the first parameter (the address) has been passed in register x0 and the array length in x1:
_sum_of_even_squares: ; x0 = start of array, x1 = length mov x3, x0 ; x3 = iterator, initialized to start add x1, x0, x1, lsl 2 ; x1 = x0 + (x1 * sizeof(int)), end of array mov w0, 0 ; w0 = total cmp x3, x1 ; check if iterator already hit the end bhs .done ; if so, done .again: ldr w2, [x3], 4 ; w2 = next element, x3 += 4 (point to next) tst w2, 1 ; test if it is even madd w4, w2, w2, w0 ; w4 = w2 * w2 + total csel w0, w4, w0, eq ; if even, update total cmp x3, x1 ; check if x3 reached end of array blo .again ; if not, continue loop .done: ret
The programming language C is slightly more abstract than assembly language. We can specify parameters directly, and use variables rather than registers. But we still treat the array as an address, pass its length as a second parameter, and be explicit about memory addressing:
int sum_of_even_squares(int* a, size_t length) { int total = 0; for (int *p = a; p < a + length; p++) { if (*p % 2 == 0) { total += (*p) * (*p); } } return total; }
The fact that there are a seemingly infinite number of approaches for even this simple problem make the study of languages interesting!
Well that was sure a lot of languages. You are now either excited or overwhelmed. But if you have patience and perseverance and take on a study of programming languages, you will reap many benefits—professional, social, and academic.
Studying programming languages will help you in many ways! For instance, you will:
Expertly wielding language allows you to communicate ideas powerfully. Programming is a form of written expression just like 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 computations elegantly and precisely. Studying not only how-to-program, but the mathematical formalisms underlying language syntax and semantics, leads to expertise in writing precise and unambiguous specifications and implementations of information structures and processes.
Understanding a variety of languages and language paradigms enables you to make informed choices when selecting the most appropriate language for a given task, as some languages do a great job expressing some kinds of tasks and do a terrible job at others. Your study will enable you to describe design tradeoffs (garbage collection or not, compile-time type safety or not) and be able to articulate how exactly languages like Rust, Swift, and Zig compete against C and C++.
Thinking in terms of language independent concepts (e.g. types, sequencing, iteration, selection, recursion, concurrency, subroutines, parameterization, naming, scope, abstraction, inheritance, composition, binding, method dispatch, and so on), rather than in one particular language’s syntactic constructs (e.g., “if statement,” “while statement,” “virtual function”), enables you to adapt to any programming environment. A time will surely come when you will author programs in a language you never even heard about.
... 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
If you know how and why a language was designed, you can:
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 “full programming language,” you might need to write your own little language as part of a larger application, for example:
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.
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?
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.
Studying programming languages may even enable you to:
Key discoveries have been made by archaeologists when digging in the ground, psychologists listening to children, computer scientists simulating language change, ethnologists watching apes, and linguists taking language apart. The pile of new puzzle pieces from their work has been added to by geneticists decoding human genomes and neuroscientists peering inside the brain. — Steven Mithin, The Language Puzzle

Programming Language Study is one of the 17 knowledge areas of computer science as defined by the ACM, IEEE-CS, and AAAI their 2023 Computer Science Curriculum.
Browse the knowledge area list now, and take a peek at the full document for the Foundations of Programming Languages knowledge area. You might find the study of language, especially some of the theoretical aspects, to be even more fundamental than the favorite of job interviewers—Data Structures and Algorithms. Language theory directly deals with how information itself is expressed, organized, and manipulated. Without a way to represent data or computation, we really wouldn’t have computer science. And the part of computer science that language theory studies is really fun: it’s about people expressing themselves in all kinds of fascinating ways.
Programming Languages is not ProgrammingLet’s face it: agents do most of the turning specifications into code. Humans engineer software systems. Getting the agents to write the right code is your job. Understanding what they write is your job. Adding your own code is your job. You need to know the deep foundations of multiple languages to be effective at your job.
But maybe you are reading these notes not because you want a job engineering software, but because you like learning. That’s wonderful too! There is a great deal that studying programming languages can tell us about human language. And knowledge is fun. And empowering.
And finally, it is only with deep theoretical knowledge that you can be positioned to create what comes next. There is always a “next” that comes beyond the current state of the art.
Self-check time! Make sure you know what a programming language is.
Yes, that link was to a Wikipedia article. Did you read the article? No? Read it. Now? Okay, let’s lay out a study plan.
An effective study of programming languages has these five dimensions:
Learn a bunch of existing languages, and understand how each expresses and implements various concepts.
Science
Study the vast array of important concepts, and understand how each is expressed in a variety of languages.
Philosophy
Learn the fundamental logical and mathematical theories that give rise to language and computation.
Mathematics
Understanding history is essential for learning any discipline and the study of programming languages is no exception.
History
Programs and Programming Languages are written by people for people. Understanding the human use and perception of languages is crucial.
Psychology
The rest of these introductory notes provide a whirlwind introduction to each of these five items, in this order:
Your study of programming languages is enhanced when done in a historical and human context. There have been many books and articles written on the History of Programming Languages (HOPL). It’s really fascinating! And super important, too. Perhaps you’ll find it exciting too.
We can’t cover the entire field here, but we can provide just enough of the highlights to give you a sense of where the languages of today came from:
Overviews are nice, but you’ll need to go much deeper. These sources will get you started:
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 than it did, if only people had carried out the visions:
Follow up with your own web searches for “History of Programming Languages” or “Evolution of Programming Languages.”
Your study of programming languages is also enhanced by becoming a polyglot—writing a lot of programs in a lot of languages, and learning each of those languages deeply. By doing so, some of the important linguistic concepts will naturally emerge. But which languages should you learn, and what should you learn about them? A good place to start is to note:
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.
Here are a few languages that for some reason or another rate as significant. The reasons why they are grouped the way they are shown here will be discussed in class:
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.
Language lists, comparisons, and taxonomies:
An important part of language study is a deep understanding of language-independent concepts. The vocabulary around programming languages is massive, so how can you organize all this knowledge you will be gaining?
Let’s begin with some critically important terminology that underlies the study of programming languages. It is time for a vocabulary fire hose. You can handle it.
A language is a structured system of communication.
A programming language is a language for expressing computation.
A computation is a determination of new information from existing information by formal, logical, mathematical, mechanical means, carried out by a sequence of steps, each of which takes a finite amount of time and resources.
The expression of a particular computation in a programming language is called a program.
CodeYou can call a program, or a collection of programs making up a system, the “code.” But please don’t call a program “the codes.”
Information is that which informs.
Usable information is representable as strings of characters over some alphabet. Today, virtually every information system uses the Unicode alphabet. Here are some example strings that give evidence that Unicode is sufficient for anything of interest to us:
91332.3e-81
⚠️ Stay BEHIND the yellow line! ⚠️
<ul><li>你好</li><li>ᎣᏏᏲ</li><li>ᐊᐃᓐᖓᐃ</li></ul>
/Pecs/Los Angeles/Berlin/Madrid//1 2/3 0/2 2/2 0/3 2///
(* (+ 88 3) (- 9 57))
int average(int x, int y) {return (x + y) / 2;}
{"type": "dog", "id": "30025", "name": "Lisichka", "age": 13}
∀α β. α⊥β ⇔ (α•β = 0)
1. f3 e5 2. g4 ♛h4++
Each character in Unicode can be encoded into bits. The most widely used encoding for characters today is UTF‑8. Information in the form of character strings is good for humans; information reduced to (encoded) bit strings is better for processing and transmission by machines. Example: The character CHEROKEE LETTER O, written as Ꭳ, is encoded in UTF-8 as 111000011000111010100011.
Information is chunked into meaningful units called values. Values can be characters, numbers (e.g., 389.221E-25), or atoms (e.g., true, false, null, north, south, blue), as well as composite values such as tuples, records, lists, sets, dictionaries, references, functions, and more. Values inhabit types which classify values by their shared behavior.
Programs are built from entities such as declarations, expressions, statements, literals, variables, functions, procedures, coroutines, threads, processes, modules, and packages. Entities refer to, store, or manipulate values.
Values vs. entitiesIt is sometimes easy to get confused between values, literals, and variables. This should help: values are pure information that exist outside of a program. Entities are program constructs that live inside the program: entities are the components from which programs are constructed. Literals and variables (both of which are entities) stand for and contain values, respectively.
The statics of a language defines how to construct programs out of entities, and specifies what we can know about a program before running it.
A language’s statics is generally defined in two parts: the syntax describes how the entities are arranged, structurally, to form a valid program, and a set of contextual rules that determine which valid arrangements are meaningful and which are not.
The dynamics of the language describes the computational effects of programs during execution.
Here’s the key idea: A running program is made up of one or more lines of execution. Each line of execution is a sequence of statements that are executed one after the other. Within each line, computation proceeds according to various types of control flow. The study of the communication and coordination between lines of execution is known as concurrency.
We’ll be studying control flow in more detail here, and concurrency here. As a teaser, we’ll be exploring the following types of control flow:
| Control Flow | Description |
|---|---|
| Sequential | Each statement is executed one after the other, in the order they appear in the program. |
| Selection | Some statements are executed based on a condition, such as an if statement. |
| Iteration | Some statements are executed repeatedly, such as in a for or while loop. |
| Recursion | A function calls itself, either directly or indirectly, to solve a problem. |
| Nondeterminism | Some statements may execute in different orders or not at all, such as in a try block with an catch clause. |
| Disruption | Some statements cause the flow of control to change abruptly, such as a return, break, continue, or throw statement. |
and the following paradigms for concurrent programming:
| Paradigm | Description |
|---|---|
| Shared Memory | The multiple lines of execution share memory and resources. Synchronization mechanisms are required to maintain safety. |
| Distribution | The multiple lines of execution each have their own protected local memory and resources, sharing nothing. The lines communicating solely via messages. |
| Coroutines | Multiple lines of execution explicitly yield to and resume each other. They all run on the same physical thread. |
| Async | One line kicks off another line without waiting for the other to finish. |
| Parallel | Multiple lines physically execute near-identical pieces of the same broken-down task, almost always on special parallelized hardware. |
Now, before we can begin to organize all this knowledge, we’ll want to ask some pretty deep, philosophical questions, that will help to motivate an ontology, or at least the beginnings of one.
When we start examining the little things, it’s nice to organize concepts into an ontology. There are many possible ontologies. Here’s a very rough one with eight top-level study areas. Don’t worry if most of these terms are completely unfamiliar.
Binding (associating names with entities), scope (the region in space or time in which a binding is active), visibility, extent (the lifetime of a binding), storage class (“where” named entities are stored), linkage (how are names accessible from the outside), defining vs. referencing occurrences, overloading and aliasing, polymorphism (static vs. dynamic), bound vs. free names, shallow vs. deep binding, static vs. dynamic scope.
Operators, precedence, associativity, fixity, arity, evaluation order (defined, undefined, short-circuit), side effects (lvalues vs. rvalues, initialization vs. assignment, assignables), eager vs. lazy evaluation, errors as values, types as values, macros.
Sequencing (comma, semicolon), selection (if, switch, match), iteration (for, while, forEach, takeWhile), recursion, procedural abstraction, functional abstraction, non-determinism, disruption (call, return, break, continue, throw, panic).
Type systems (fixed or extensible, extra-lingual vs values, first-class or not), basic, sum, and product types, types vs. classes vs. typeclasses, type expressions, type checking, type inference, type coercion, type equivalence, type compatibility, static vs. dynamic typing, strong vs. weak typing, manifest vs. inferential typing, nominal vs. structural typing, parameterized types (generics), covariance, contravariance, invariance, dependent types, higher-kinded types, universes.
Procedures (abstractions for statements) vs. functions (abstractions for expressions), first-class functions, higher-order functions, closures, currying, partial application, function composition, lambda expressions, anonymous functions, function signatures (names, modes, types, patterns, defaults, rest parameters), parameters and arguments (exactly one parameter? Or zero to many? Exactly one return value? Or zero to many?), parameter association (positional vs. named, value vs. reference vs. name), recursion (direct vs. indirect), tail recursion.
Modules, packages, units, namespaces, classes, object orientation, import and export, open vs. closed, encapsulation, separate compilation.
“Bob Barton, the main designer of the B5000 and a professor at Utah had said in one of his talks a few days earlier: "The basic principle of recursive design is to make the parts have the same power as the whole." For the first time I thought of the whole as the entire computer and wondered why anyone would want to divide it up into weaker things called data structures and procedures. Why not divide it up into little computers, as time sharing was starting to? But not in dozens. Why not thousands of them, each simulating a useful structure?” — Alan Kay
Introspection, reflection, eval, decorators, metaclasses.
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 as expressed in the study of logic and various mathematical and computational theories.
Syntax and semantics are part of the formal specification of a programming language. But there’s another dimension that is equally important.
Pragmatic concerns must guide your design of a programming language, that is, if you want it to be easy to read, easy to write, and able to be implemented efficiently (all of which you do). Pragmatics encompasses:
We can think of a language’s value system as what it is that the language wants to be. The values stem from the vision of the designers and ultimately play a role in making a language learnable, usable, and maybe even successful. To get a sense of what this all means, see Ashley Williams’ amazing talk subtitled “How language values shape the features we build and the journeys we take to design them.” It focuses on JavaScript and Rust, but the ideas are universally applicable:
Reasonable people know that all languages have their pros and cons. But why are some things pros and other things cons? Under what criteria can we evaluate programming languages?
There are certain technical criteria. For example, is the language:
DO 10 I = 1 . 5 PRINT * "Hello" 10 CONTINUE
Also see Wikipedia’s Programming Language Comparison article.
There are non-technical, subjective criteria, too. Good and successful are not the same! Success comes from:
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. At the beginning of these notes, we saw a function to return the sum of the squares of even numbers in a list in a bunch of different languages. Revisit those examples now with an eye toward what you think the designers of each language want you to be able to, or not be able to, express. What constraints are they placing on you?
Related to expressiveness is the idea of code as art, or art as code. Here’s a discussion of what this actually means:
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 March, 2024:

RedMonk gives these rankings:
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.”
The YouTube channel Context-Free has a video on language popularity (covering the Languish app):
Interesting how Tiobe ranks Ada as #9 but Languish places it at #161. Go figure.
But what makes a language popular? MPJ has thoughts:
You can’t have everything, it seems:
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.
Here are some questions useful for your spaced repetition learning. Many of the answers are not found on this page. Some will have popped up in lecture. Others will require you to do your own research.
IntStream.of(a).filter(x -> x%2==0).map(x -> x*x).sum()sum(x**2 for x in a if x % 2 == 0)

We’ve covered: