This site is deprecated and all the content has moved to AppliedTechnology
Programming languages come in many different flavors, just as human languages. Each language gives you different opportunities and possibilities to express a solution to a problem. Again just as human languages - let's examine that a bit deeper.
For example, consider Chinese - a language that is made up of signs. There's a sign for house and another for wagon. If you want to express the concept of a mobile home - you will make a new sign that is the combination of house and wagon (I presume - I don't know Chinese.)
In the language of heroes and Gods (Swedish) we instead use letters to build words. The word for "house" is h, u and s put next to each other, with spaces before and after - to make the word hus. Wagon is vagn. If you want to represent the concept of mobile home you would combine the letters into a new word husvagn.
The type of language decides how we describe things and how we think about things.
There are a few different types of programming languages that, in a similar fashion as human languages, affects how we think about the task at hand. This post will describe two of the two big types of programming languages, but there are many more types.
You can't decisively say that one type of language is better than another language; they are all good in the things they are designed to do. You can solve just about any problem using any kind of language (at least the general purpose programming languages) but they all have their strengths and weaknesses.
Before we start; the topics that we describe here are both 10-week courses at university and then took me (Marcus) at least 2-3 years more to use to my advantage in real life. This will be a very shallow brush on the surface.
Object oriented languages (OOP - object orient programming) is obviously based around the concept of objects. These objects contain data and operations to work on that data.
Famous OOP languages are C#, Java, and Smalltalk. OOP was made popular in the 80-is with the rise of the graphical user interface, that has a good fit for object orientation.
When writing OOP programs we represent our code as classes. Classes are templates for creating objects. Classes describe how the objects should be structured and behave. We describe the problem we are working with as structures of these classes. Classes can inherit functionality from other classes.
An (overused) example hopefully clarifies what this means. Let's say that we are building a register for animals:
class Animal {
constructor(name) { this.name = name }
getName() { return this.name.toUpperCase() }
}
class Dog extends Animal {
constructor(name) { super(name) }
makeYourSound() { return `${this.getName()} says WOOOF` }
paws() { return 4 }
}
class Bird extends Animal {
constructor(name) { super(name) }
makeYourSound() { return `${this.getName()} says Tweet-tweet` }
beekLength() { return 2.5 }
}Let's go through this example and see a lot of the OOP paradigms in action. These are the different way that we think about a problem and we will see constraints and opportunities.
-
On line 1 we write a class
Animalthat will hold all the information that is common for all animals, for example, a name.- We have a special method called
constructorthat is used to create new objects of this class. Notice that we need to supply anameto this, ensuring that all animals will have aname. - We can get the name of the animal by calling
getName. This is called encapsulation meaning that we hide the internal representation and only allow users of this class to get names through this method. Hence it will always be uppercased
- We have a special method called
-
On line 6 we write another class
Dog, a specialization of animals. This will hold all the information about Dogs.- The
DogclassextendstheAnimalclass, meaning that inherits all the data and methods fromAnimal.- Hence a
Doghas a name, that is defined onAnimal
- Hence a
- In order to create a
Dogwe will use the constructor, passing thenameas before. But we will call thesuper(name)method and pass its name. This is how we callAnimalsconstructor to set up the animal in the correct way. - For example, a dog has a
pawsmethod that returns the number of paws - The
makeYourSoundmethod will return a string that represents how the Dog sound. Notice that Dogs always saysWoof. An abstractAnimaldon't have amakeYourSoundmethod since we don't know how those (abstract animals) sound. It doesn't make sense talking about sounds for animals, without knowing what type of animal it is.
- The
-
Line 12 defines another specialization of
AnimalcalledBird. -
It behaves like an
Animalbut has a specialization implementation ofmakeYourSoundthat makes it sound like a bird. -
It also has a
beekLengthmethod that returns the length of the beek, which will be the same for all birds (that is strange but I ran out of imagination)
Let's use these classes and create a few concrete objects:
const d = new Dog('Fido')
console.log(d.makeYourSound()) // Logs: FIDO says WOOOF
console.log(d.paws()) // 4
const b = new Bird('Jacko')
console.log(b.makeYourSound()) // JACKO says Tweet-tweet
console.log(b.beekLength()) // 2.5We create an instance of the Dog class by using the new keyword, that will call the constructor passing Fido as the name of the Dog. The instance is called an object and we store it in a variable called d. There's only oneand has the nameFido. We can see that by calling the makeYourSound()` method.
The same behavior can be witnessed as we create a Bird object const b = new Bird('Jacko'). The makeYourSound method now returns the sound of the bird.
Now, we know that everything inheriting from Anmial have a getName method so we can use that to our advantage. Let's create an array of Animals:
const d = new Dog('Fido')
const b = new Bird('Jacko')
const animals = [d, b]
animals.forEach((a) => console.log(a.getName()))
// FIDO
// JACKOBoth d and b is Animals since they extends animals and we can safely use getName to logs their individual names.
But even cooler is that we can use the makeYourSound method on both of the animals. They both have them and we don't need to care what kind (or type) of animal it is - as long as we know that it has a makeYourSound method.
animals.forEach((a) => console.log(a.makeYourSound()))
// FIDO says WOOOF
// JACKO says Tweet-tweetNow it a behaves differently depending on what type it is, but our code doesn't care. This is called Polymorphism.
(Notice that since JavaScript (that the code is written in) doesn't have basic OOP features has overrides and interfaces the example is not complete, but showing a point.)
Functional programming (FP) is oriented around functions that you pass data too. We strive to write immutable (once defined, unchangeable) data and pure (stateless) functions. Program is made up by composing many small functions into a larger whole.
FP is much older than OOP and dates back to the childhood of computer science (1950-is). Commonly used functional programming languages are JavaScript, Haskell and Erlang (or Elixir).
In FP our function operates on data (objects) and we seldom describe the template (classes) for such data. Function themselves are first class citizens and very often is passed as parameters to other functions, building so-called high-order functions (functions that take functions as parameters).
Again, an example might be useful:
const dogPrinter = d => `${d.name} has ${d.paws} paws and says WOOF`
const birdPrinter = b => `${b.name} says tweet-tweet with her ${b.beekLength} cm beek`
const animalPrinter = a => {
switch (a.type) {
case 'Dog':
return dogPrinter(a)
case 'Bird':
return birdPrinter(a)
}
}
const animalSorter = (a, b) =>
a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1(Yes, I'm using the arrow function syntax throughout my example - read up on it if it confuses you. Also, I'm not a fan of semi-colons where they are not needed. So I have not used them - sue me! :))
Let's go through the code line by line
- Line 1 creates a function called
dogPrinterthat returns a string representing a dog.- The function presupposes that we will pass it data that has
.name.paws. We will also always return 'WOOF'. Since this is the dog printer we can do that - Notice that it doesn't change any data and only operate on the data passed to it. If I send the same` to the function it will return the same result. This is what's known as a pure function
- An unpure function would, for example, have returned the date of the day we ran the function. It would have changed between calls.
- The function presupposes that we will pass it data that has
- We then (line 2) create a
birdPrinterthat does similar things for birds. - Line 3 sets up an animal printer that checks the
.typeand then calls the correct print function. If you passanimalPrintersomething that doesn't have a.typeproperty or if theapassed to the function not isDogorBirdit will fail. - Line 11 creates an function called
animalSorterthat returns 1 ifa.nameis greater thanb.name. This will be used to sort and tell us which of the animals (aorb) that comes before another in an alphabetically sorted list.- Notice that
animalSorterneeds data passed to it with.name-properties. It doesn't care if it is animals or not. Hence it could be called … name sorter or something like that. - I am using the ternary operator to make the code into one single line. As the functionality of each function is very little, we often can make one-liners and then combine it into more advanced features
- Notice that
Let's use the functions and put them together. First, we need some data to operate on:
const animalList = [
{ name: 'Jacko', type: 'Bird', beekLength: 2.5 },
{ name: 'Fido', type: 'Dog', paws: 4 }
]
animalList
.sort((a, b) => animalSorter(a, b))
.map(a => animalPrinter(a))
.forEach(el => console.log(el))- The
animalListis an array of animals. Notice that they are not formatted the same, but have some common properties, such asnameandtype- This is important since our functions are using them to do different things
- We run the functions in a chain operating on the array
animalList- First we will sort the list by calling the
animalSorterfor each element..sorttakes two parameters, two elements from theanimalListand just call into theanimalSorter.sortwill go through the entire array and callanimalSorterfor all the elements and create a new sorted array, according to theanimalSorterrule- At the end
.sortwill return a new sorted array
- Then we will get the sorted array and pass it to
.map..maptakes an array and calls the function for each element to produce another, new array.sortis actually a special version of.map(.mapis a generic version calling a function for each element in an array, returning a new array)
- We will call the
animalPrinterfunction for each animal. animalPrinterwill return a string for each, meaning that.mapwill return an array of strings
- We will use this array of string and pass each of those string to
console.log, using the.forEachfunction- The
.forEachfunction that just executes a function per element but doesn't return anything.
- The
- First we will sort the list by calling the
There's an even tighter way to call the functions above, that you might see. If all there parameters that a function takes are the same as the parameter of the calling function we can just write the name of the function.
So this part:
animalList
.sort((a, b) => animalSorter(a, b))
.map(a => animalPrinter(a))
.forEach(el => console.log(el))Could be written like this:
animalList
.sort(animalSorter)
.map(animalPrinter)
.forEach(el => console.log(el))Notice some of the functional paradigms in action here:
- Our small functions are composed into bigger, more advanced functions
- How the data is passed from function to function like water through a pipe
- Notice that we don't change the data passed to the function but create new data that we return to the next function
- How each function is small and simple and also only depend on the data passed to it - hence it is pure.
- Each function is very easy to test and reason about
From Wikipedia we can read:
JavaScript has curly-bracket syntax, dynamic typing, prototype-based object-orientation, and first-class functions.
and
As a multi-paradigm language, JavaScript supports event-driven, functional, and imperative (including object-oriented and prototype-based) programming styles.
This means …
JavaScript is not really OOP but rather more FP. Meaning you can do most of the things needed to call it an FP-language but not all of it (pattern matching is lacking for example).
You can do some of the OOP things too but it has an even poorer fit.
Thinking in FP paradigms and patterns is most useful when doing JavaScript development.
Object-oriented programming (OOP) is built around the object that encapsulates data and methods that operate and change that data. We represent the problem we are working with as classes (templates for objects) that inherit functionality from each other.
Functional programming (FP) is built around functions that operate on data. Functions are often passed as parameters to other functions (high-order functions) making up more advanced features from simpler. Functions should be stateless and operate on data passed to it (pure function). The functions should not modify the data passed to it but rather return new data (immutable).
I hope it's a bit more clear what the difference between the two paradigms are. If not, here's a lovely graphics that probably is a much better summary than I can do. It is from https://www.educba.com/functional-programming-vs-oop/ and is really good.
