Finding the Skeleton Buried In The Code
Derek D.
Posted on March 10, 2021
So, that title is total clickbait and I apologize for that. Reading it you think this is going to be a story of how I uncovered some nasty hacks buried deep within a legacy code base that changed the very fabric of reality itself. That's not what this article is about though. This article is about seeing through language specific syntax and discovering the fundamental skeletal structure of programming constructs like conditionals, loops, and classes.
My initial thoughts for this article came to me early in 2020 while I was writing Go vs Python a Technical Deep Dive. I figured if I wanted any authority on the topic I should be able to write some Go. So I set aside a weekend to go (pun intended) through the official Tour of Go. At the end of the weekend I didn't just know the features, and data types of Go I could write syntactically correct Go from memory. This made me wonder why did my first programming language, Java, take 6 months to learn but Go only took a weekend? My theory is there is a fundamental skeletal structure in the core building blocks of every programming language, and if you can understand those you can teach yourself and programming language in literally a weekend. It's kind of like knowing Latin. When you know what every other language was derived from those languages become easier to learn.
Without further gilding, the lily here is an in-depth examination of programming's core constructs that cuts away language specific syntax and shows the skeletal structure of programming. These are the constructs that will be examined in this article.
- Code Comments
- Output
- Variables
- Conditionals (if, else if, else)
- Loops (for, while)
- Functions
- Classes/Objects
- imports
Code Comments
I always start with code comments so I can use them to explain code in later sections.
Code comments are important because it's how you leave notes, explanations, and documentation in source code for yourself and other developers. A code comment always starts with some special character(s). In most languages, a comment starts with //
, and everything on that line following //
is ignored by the compiler/interpreter. Some languages, like Python use #
to start a code comment. Regardless of the character(s) used to start a comment every language has them and they are incredibly useful. Here's an example in Python, Javascript and Go.
Python
# This is a comment.
Javascript & Go
// This is a comment in both javascript and go
There are also multi-line comments. Most languages start a multi-line comment with /**
and end them with **/
very similar to opening and closing tags in HTML. Here are examples of multi-line comments in Python, Javascript, and Go.
Python
"""
Python is weird and uses the triple quote for mutli-line comments.
Note there is a triple quote used to close the comment.
"""
Javascript & Go
/**
* This is a multi-line comment in Javascript and Go
* Note each line starts with a star but it is not needed as long as the
* comment exists between the opening and closing tags
**/
Outputs
Outputs are ultimately what benefit the user. Without output, a program could solve the most difficult problems in the world but the operator would have no idea of what the solution was. Output can be something printed to console, a file written to disk, or technically event something printed on paper. There is always one built into the language though and that's printing to console.
The first program many developers write is Hello Word. Hello World is a simple program that prints the string of characters "Hello World!" to console. Every programming language has some built-in function that handles this for you, all you have to do is pass the string of characters you want to be printed. In Python that function is quite literally print()
, Javascript has console.log()
while Go blends the 2 with fmt.Println()
. Here is Hello World in each of these languages.
Python
print('Hello World!')
Javascript
console.log('Hello World!')
Go
fmt.Println("Hello World!")
In these examples Python's print()
is a function while Javascript's log
and Go's Println
are methods on an object. Objects and methods will be discussed in more depth later.
Variables
Variables are the first building block that can also be a stumbling block. Comments and Outputs are very static. It's easy to understand what they do. Variables change though, it's why they are called variables. There are only 2 things you can do with a variable; assign it a value and reference that value.
Here's an example of assigning a value to a variable.
Python
name = 'Derek'
Javascript
var name = 'Derek' // A variable accessible throughout the entire program
let name = 'Derek' // A variable accessible only within the current scope
const name = 'Derek' // A variable whose values can't change known as a constant
Go
var name str = "Derek" // Creates a variable of type str with the value Derek
var name = "Derek" // Creates a variable that derives the type str from its value Derek
name := "Derek" // Shorthand for creating a variable that derives its type from its value
Here you see why Python is so popular, there is only one way to assign variables. You don't have to worry about data types or if the variable is accessible globally or not. At this point, it's sufficient to understand an assignment statement has exactly 3 parts. The variable
, the operator
, and the value
. This is still true for statements with expressions in them like a = 2 + 2
. For these kinds of statements, the expression is evaluated and the result gets assigned to the variable
. The expressions that appear on the right-hand side of the operator can get incredibly complex but at the end of the day, the entire expression is evaluated into a single value before the assignment occurs.
Conditionals (if, else if, else)
Conditionals are how your program makes decisions. Conditionals have 2 parts; the condition statement that evaluates to true or false, and the condition body which only executes when the condition statement evaluates to true. Conditionals can be chained using if/else if/else
condition statements.
If conditionals are the most common conditional and start every chain of conditionals.
Here's an example of an if statement.
Python
count = 1
if count > 0:
print('count is positive')
Javascript
const count = 1
if (count > 0) {
console.log('count is positive')
}
Go
count := 1
if (count > 0) {
fmt.Println("count is positive")
}
In these examples if count > 0:
and if (count > 0)
are the condition statement. and the print statements, print('count is positive')
etc. are the condition body.
The key syntax differences are Python doesn't surround the expression in ()
while Javascript and Go do, and Python uses a :
to indicate the end of the condition statement and the start of the condition body, while Javascript and Go just wrap the condition body in {}
. What's known as the conditional expression count > 0
is the same in all 3 languages though.
To continue a conditional chain an else if
conditional can be used after the body of a preceding conditional. The else if
conditional will only be evaluated if the condition statement that comes before it evaluated to false.
Here are the previous examples now with else if statements.
Python
count = 10
if count > 5:
print('count is positive')
elif count < 0:
print('count is negative')
Javascript
const count = 1
if (count > 0) {
console.log('count is positive')
} else if (count < 0 ) {
console.log('count is negative')
}
Go
count := 1
if (count > 0) {
fmt.Println("count is positive")
} else if (count < 0) {
fmt.Println("count is negative")
}
The key difference here is Python uses the elif
keyword instead of else if
. elif
doesn't do anything special it's just shorthand for else if
.
Else conditionals are used to end conditional chains. An else
conditional does not have a conditional statement since it only executes when every preceding if
and else if
condition statement in the chain evaluates to false. This is why else
statements are commonly used to define default behaviors in a program.
Here are the examples with else statements.
Python
count = 10
if count > 5:
print('count is positive')
elif count < 0:
print('count is negative')
else:
print('count is zero')
Javascript
const count = 1
if (count > 0) {
console.log('count is positive')
} else if (count < 0 ) {
console.log('count is negative')
} else {
console.log('count is zero')
}
Go
count := 1
if (count > 0) {
fmt.Println("count is positive")
} else if (count < 0) {
fmt.Println("count is negative")
} else {
fmt.Println("count is zero")
}
Loops
Loops are where the languages start to diverge a little bit, mostly because there are types of loops available in some languages and not others, however, those loops that are available in multiple languages generally have similar syntax.
Loops are used to loop over and execute the same code until a condition is true. The main loops are for
and while
loops which we will start with.
The anatomy of while loops are similar to conditionals. There is a condition statement that evaluates to true or false and immediately following that is the loop body which is executed every time the loops condition statement evaluates to true.
Here is an example of a while loop that will search for the letter a
in a jumbled up alphabet. Note Go only has for loops so there is no code snippet for while loops in Go.
python
needle = 'a'
haystack = 'rsthidlmjpqknefgabczowxyuv'
curr = ''
index = 0
while curr != needle:
curr = haystack[index]
index += 1
print('found needle at index', index)
Javascript
needle = 'a'
haystack = 'rsthidlmjpqknefgabczowxyuv'
var curr = ''
index = 0
while (curr != needle) {
curr = haystack[index]
index += 1
}
console.log('found needle at index', index)
The same differences from conditionals apply here as well. Python separates the loop condition statement with :
while Javascript wraps its condition statement in ()
and the body in {}
.
For loops are tend to be more verbose than while loops and can come in 2 forms. The classic for
loop and a for each
/for in
loop.
A classic for
loop has 2 parts to it the for statement and the loop body. Subsequently, the for statement is made up of 3 parts, which I call the initializer, the condition, and the step. The initializer sets up a variable used to keep track of the number of
iterations, the condition determines if the loop body should be executed again, and the step which moves the variable created in the initializer to its next step.
Here is an example of a classic for loop in Javascript and Go since Python only has for in
loops.
Javascript
count = 0
for (var i = 0; i < 10; i += 1) {
count += 1
}
console.log('count is', count)
Go
count := 0
for i := 0; i < 10; i += 1 {
count += 1
}
fmt.Println("count is", count)
In these examples var i = 0
and i := 0
are the initializer, i < 10
is the condition, and i += 1
is the step. Everything between the {}
is the loop body. Both of these programs will print out the value 10.
For each
and for in
loops are used to iterate over every element in a collection. They have 2 parts as well the for in/for each statement and the loop body. Just like a classic for loop for each/for in
statements can be broken up into parts as well. These parts are the iteration variable and the collection. The collection is just as it sounds. It's a collection of values that will be iterated over. The iteration variable is a variable that holds the current value being looked at in the collection.
Python
numbers = [1, -1, 10, -5, 8, 9, -4]
positives = 0
negatives = 0
for n in numbers:
if n >= 0:
positives += 1
else:
negatives += 1
print('Negatives', negatives, 'positives', positives)
Javascript
const numbers = [1, -1, 10, -5, 8, 9, -4]
let positives = 0
let negatives = 0
for (const n in numbers) {
if (n >= 0) {
positives += 1
} else {
negatives += 1
}
}
Go
numbers := []int{1, -1, 10, -5, 8, 9, -4}
positives := 0
negatives := 0
for _, n := range numbers {
if n >= 0 {
positives += 1
} else {
negatives += 1
}
}
fmt.Println("Negatives", negatives, "Positives", positives)
In these examples, there is some magic performed by the language in that each iteration of the loop the next value in the collection is stored in the iteration variable. In this case n
is the iteration variable and [1, -1, 10, -5, 8, 9, -4]
is the collection. This is usually a mind-bending concept the first time you see it so for those of you just getting started. on the first iteration of the loop n
is equal to 1 and the second iteration n
is equal to -1.
Functions
Functions are very similar to algebraic functions. They receive input(s) and produce an output. Consider the following algebraic function that doubles the value of x.
f(x) = x * 2
f(2) = 2 * 2
f(10) = 10 * 2
f(x) could be called the function declaration (i.e. what the function does) f(2) calls the function with the input 2, which results in the output 4 f(10) calls the function with the input 10, which results in the output 20
This mathematical concept of doubling an input can be expressed in Python like so.
def double(x):
return x * 2
A function has 2 parts, its signature, and its body. The signature is made up of the function name and the parameters that the function accepts. In some languages, the data types of the parameters and the data type returned by the function are also part of the signature. Here are the declarations for the double function in Javascript and Go since you've already seen it in Python.
Javascript
function double(x) {
return x * 2
}
// Note this is the same thing just using ES6 arrow functions
double = x => x * 2
Go
func double(x int) int {
return x * 2
}
def double(x)
, function double(x)
and func double(x int) int
are the signature, while return x * 2
is the body. Go is one such language where the data type of the parameters and return value are used in the signature. That's why you see x int
for the parameter and int
following the closing parens )
for the return type.
The functions are called the same way in all the languages.
Python
double(2) # returns 4
double(10) # returns 20
Javascript
double(2) // returns 4
double(10) // returns 20
Go
double(2) // returns 4
double(10) // returns 20
Classes and Objects
Classes are used to model real-world objects/concepts. Some languages, like Go, have things that are class like while some others, mostly functional programming languages, don't have classes and objects at all.
My favorite example to explain classes is a table. That's right like your run of the mill coffee or dining room table. Classes are complex so I can't say, "here is the format all classes use" as I could for conditionals, loops, and functions. Using tables as an example, however, makes it simple to understand what components make up a class. There are 3 basic components to every class. The constructor, properties, and methods.
The properties are fields, properties, or attributes of the thing you are modeling. In our case, this is a table and those attributes might be height, length, width, height, and legs representing the number of legs a table has.
The constructor is a special method on the class that builds an instance of the class which is an object. Here is an example of a Table class that defines the height, width, length, and legs properties and a constructor for constructing instances of the class. Remember instances of the class are objects.
Python
class Table:
def __init__(self, height, width, length, legs):
self.height = height
self.width = width
self.length = length
self.legs = legs
Javascript
class Table {
constructor(height, width, length, legs) {
this.height = height
this.width = width
this.length = length
this.legs = legs
}
}
Go
type Table struct {
Height int
Width int
Length int
Legs int
}
In these examples, Python and Javascript have explicit constructor methods while Go has an implicit constructor. In Python, the constructor is __init__
and in Javascript constructor
. These methods take in parameters that are used to set the properties for the instance of the class being constructed. This is seen with self.height = height
in Python and this.height = height
in Javascript. Go does the same thing implicitly which is why no constructor needs to be defined in Go. Here is an example of creating 2 tables, myTable
and yourTable
.
Python
myTable = Table(3, 4, 8, 4)
yourTable = Table(3, 4, 8, 4)
Javascript
myTable = new Table(3, 4, 8, 4)
yourTable = new Table(3, 4, 8, 4)
Go
myTable := Table(3, 4, 8, 4)
yourTable := Table(3, 4, 8, 4)
I like this example because it's easy to see that mytable
and yourTable
are not the same table even though they have the same height, width, length, and the number of legs. This is an important concept for Classes and objects since they help to model the real world. After all, when I bought my dining room table I didn't buy every instance of that table just because they had the same properties as mine. More importantly, though tables with different properties can be represented using the same Table class. Here's an example of creating a coffee table and dining room table.
Python
coffeeTable = Table(2, 2, 4, 4)
diningRoomTable = Table(3, 4, 8, 4)
Javascript
coffeeTable = new Table(2, 2, 4, 4)
diningRoomTable = new Table(3, 4, 8, 4)
Go
coffeeTable := Table(2, 2, 4, 4)
diningRoomTable := Table(3, 4, 8, 4)
In this case, it's obvious that coffeeTable
isn't diningRoomTable
because they don't have the same values. An important take away though is regardless of the table represented a table in its general abstract sense can be described as having a height, width, length, and number of legs no matter what those values are. The object's properties can be accessed like so.
Python
print(coffeeTable.height)
Javascript
console.log(coffeeTable.height)
Go
fmt.Println(coffeeTable.Height)
The final components to a class are methods which we briefly talked about with constructors. A method is just a function bound to a class meaning the function cannot be called without referring to the class. Calling a method syntactically is a hybrid between calling a function (i.e. foo()
) and accessing an object property (i.e. myTable.height
). Here's the code for the rather contrived example of folding tables.
Python
class Table:
def __init__(self, height, width, length legs):
self.height = height
self.width = width
self.length = length
self.legs = legs
def fold(self):
self.length = self.length / 2
def unfold(self):
self.length = self.length * 2
Javascript
class Table {
constructor(height, width, length legs) {
this.height = height
this.width = width
this.length = length
this.legs = legs
}
function fold() {
this.length = this.length / 2
}
function unfold() {
this.length = this.length * 2
}
}
Go
type Table struct {
Height int
Width int
Length int
Legs int
}
func (t Table) fold() {
t.length = t.length / 2
}
func (t Table) unfold() {
t.length = t.length * 2
}
There are 2 main takeaways from these examples. The first is that methods are defined the same way as functions. Functions and methods both use the def
keyword in Python, function
in Javascript, and func
in Go. The difference is they are bound to the class. This is shown very explicitly in Go with the (t Table)
in the signature of fold and unfold functions. It quite literally binds those functions to that struct. Python's use of self
as the first parameter is a little less explicit but does the same thing. In Javascript, it's completely implicit but it's still happening and you can see that by the use of this
. The second takeaway is that self
in Python, this
in Javascript, and t
in Go are references to the object that the method was called from. So for example, if I had 2 variables myTable
and yourTable
representing 2 coffee tables and fold is called on myTable
only the length for myTable
will change. Here's the code demonstrating this.
Python
myTable = Table(4, 4, 2, "Wood", 4)
yourTable = Table(4, 4, 2, "Wood", 4)
myTable.fold()
print(myTable.length) # prints 1
print(yourTable.length) # prints 2
Javascript
myTable = Table(4, 4, 2, "Wood", 4)
yourTable = Table(4, 4, 2, "Wood", 4)
myTable.fold()
print(myTable.length) // prints 1
print(yourTable.length) // prints 2
Go
myTable := Table{4, 4, 2, "Wood", 4}
yourTable := Table{4, 4, 2, "Wood", 4}
myTable.fold()
print(myTable.Length) // prints 1
print(yourTable.Length) // prints 2
Imports
Imports come in 2 varieties, full and partial. A full import is the simplest and most common import bringing in an entire module of code. They look like this.
Python
import datetime
Javascript
import moment
go
import "time"
Sometimes you only want a specific variable, function or class from a module and that's when you use a partial import. Go doesn't support partial imports but they look like this in Javascript and Python.
Python
from datetime import timedelta
Javascript
import { timedelta } from moment
Conclusion
This is by no means a comprehensive guide to learning programming. What is hopefully has done though is provided a mental model of programming languages themselves that you can use to teach yourself a new language and/or decipher code written in any language. As always I want to hear from you and if you enjoyed the article I encourage you to follow me on Twitter @d3r3kdrumm0nd and subscribe to the Namespace Podcast that I co-host.
Posted on March 10, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024