A language agnostic introduction to programming
Programming, at its core is simple. It’s telling a computer what you want it to do, and how to do it. Computers, at their core are really, really dumb. They are, in reality, rocks we tricked into thinking. Your computer, when it’s behaving as it should, will only do what you tell it to. Programming is the art, and science of telling a rock how to think. What I’m going to attempt is to provide a language agnostic guide to teaching a rock to think.
We’ll begin with the absolute basics. In order to teach a rock to think you have to first tell it what to think about. This is data. Programming is telling a computer what to do with data. In order to do that we need instructions. These instructions take the form of:
- Types
- Variables
- Conditionals
- Loops
- Functions
Types⌗
Types are simple. They’re descriptions of data that a computer can understand, and they’re absolutely fundamental to programming. I’m simplifying things, probably a sinful amount, I’ll admit, but I genuinely believe things are simple.
An example would probably be helpful. Think back to math class for a minute. In math there are integers, real numbers, fractions, and decimals. These are types, and integers, and decimals (of some sort) are also builtin types in most programming languages. Other builtin types include things like
- Strings or “text”
- Vectors, which are collections of numbers
- Arrays, which are collections of any other types indexed from 0 usually
- Dictionaries or hashes, they’re the same thing just by different names in different languages. They’re collections of other types keyed by a user defined identifier.
Conditionals⌗
Conditionals are also simple. They’re a way to tell the computer to make decisions based on information. If a condition is true do one thing, otherwise do something else. It’s that simple. These rely on the data and types I mentioned before, along with simple operators like equals, less than, greater than, and others to evaluate the data you’ve provided.
Loops⌗
Loops are a way to make multiple decisions or process lots of data in an efficient manner. They usually take the form of either for loops, or while loops. These differ primarily in when the escape condition is evaluated. In for loops the escape condition is evaluated at the start of each loop, in while loops the condition is evaluated somewhere inside the loop. For loops often take the form “for x condition do y” or “for x items in y collection do z operation”. While loops usually take the form of “while x condition is true do y action”
Functions⌗
Functions are a way of grouping actions and organizing your code. It says to the computer, “Don’t execute this yet, but remember these instructions because I’m going to need them later.” Functions can contain any of the previously mentioned control structures, or data types, and can do any operations you can think of. Generally, it’s considered best practice to keep functions short and single purpose. This way if a change is necessary it only affects a small part of the code, in theory. In practice changing a function will change if a change is necessary it only affects a small part of the code, in theory. In practice changing a function will change if a change is necessary it only affects a small part of the code, in theory. In practice changing a function will change if a change is necessary it only affects a small part of the code, in theory. In practice changing a function will change other parts of the program, especially if the function has side effects.
Side effects, for the uninitiated, are things that a function does that directly affect the world outside the function. For example, if you have an list of numbers and a function that doubles numbers in a list, there are two ways to do this. First is to modify the list in place. So…
list_of_nums = 1, 2, 4
double_nums(list_of_nums)
# list_of_nums = 2, 4, 8
This is an example of a function with side effects. In the example above list_of_nums occupies the same place in your computers RAM the whole time. This can cause problems. If your program is depending on list_of_nums to have a particular set of values and another function modifies it, your program is going to run into problems.
The other way to solve the same “double numbers in a list” problem is with what we like to call a “pure function”. A pure function has no side effects and always returns the same result for the same input. So the above example could be implemented like this:
list_of_nums = 1, 2, 3
doubled_nums = double_nums(list_of_nums)
# doubled_nums = 2, 4, 6
# list_of_nums = 1, 2, 3
This can be, and usually is, more memory intensive because doubled_nums occupies a new space in memory that is distinct from the space list_of_nums occupies. This can be a disadvantage. Often however, that disadvantage is outweighed by the advantages this model provides.
- It’s thread safe if done properly. This means that multiple processes can operate on the same data at the same time (usually) without causing issues.
- You’re not modifying data in place, this provides the above benefit, along with easier to solve bugs because you know what your data is.
- Easy automated testing because your function always returns predictable results.
- As mentioned above, predictability. You can be confident that a variable is always going to have the same value.
Actually making a computer do something useful with this knowledge⌗
Now that you have the knowledge of the building blocks of programming, it shouldn’t be too difficult to pick up a programming language and apply what I’ve outlined above. I say this knowing full well the difficulty of actually doing that. Writing a program that works is a matter of combining the primitives.
Means of combination⌗
When it comes to data, you are already familiar with all the means of combination available to you:
- Addition
- Subtraction
- Multiplication
- Division
- Insertion
- Deletion
All of the above are primitive operations or built-in functions in every common programming language today. Where things get interesting is when you start combining functions. This is usually done via composition. Composition is simply using smaller functions to build larger ones. You may remember composition from algebra where it often took the form:
f(x) = x^2
g(x) = x + 3
Find f(g(x)). (I believe the result of this would be (x + 3)^2)
In programming it’s usually easier than the algebraic example above. Usually it looks something like this:
func1(): some operation
func2(): some other operation
func3() func1(func2())
# func3 gives the result of calling func2 on the result of func1
As you keep building function on top of function you build new ways to modify the data. This is where programming gets challenging. Eventually it becomes very difficult to follow data as it changes. This is one source of bugs along with things I’ve already mentioned such as thread safety, and mutable data.
From here it gets more complicated, and covering the specific complexities is beyond the scope of this article as most of them are language specific. However, now you should have a basic grasp on the concepts.