Learning New Programming Languages by Building on Existing Foundations
September 04, 2018
One of the things about being a developer is that you’re never done learning. You might have a favorite language, or framework, or library, but in all likelihood you won’t be able to use those exclusively. You may love JavaScript, but the project you’re working on may demand Python. You may be great with Perl, but your company’s codebase may be in C++. To new developers, the idea of learning a new language can be very daunting, especially if there are deadlines hanging over your head. That’s the bad news. The good news is that learning a new language doesn’t have to be hard. If you use your existing mental models as a foundation, you’ll find that learning a new language is mostly a matter of extending existing knowledge rather than starting from scratch.
Start with Similarities
At their core most programming languages are based on the same set of foundational principles. Implementation varies, but rarely is one language so different from another that parallels can’t be drawn. The key then to learning and understanding a new language is to first identify similarities and then expand on your understanding of a concept if/where necessary. Consider, for example, data types and variables. Every language has a way to define and store data so that it can be used throughout a program. As a result, when you’re learning a new language, figuring out how to define and use variables will likely be one of your first tasks. Let’s look at an example in two different languages: Ruby, which is an interpreted language that uses dynamic typing; and, C#, which is a compiled language that uses static typing.
my_int = 8
my_decimal = 8.5
my_string = "electron"
puts "My int is: #{my_int}"
puts "My float is: #{my_decimal}"
puts "My string is: #{my_string}"
using System;
public class Program
{
public static void Main()
{
int myInt = 8;
double myDecimal = 8.5;
string myString = "electron";
Console.WriteLine("My int is: {0}", myInt);
Console.WriteLine("My float is: {0}", myDecimal);
Console.WriteLine("My string is: {0}", myString);
}
}
Imagine that you are an experienced Ruby developer and you want to learn C#. Looking at these snippets, the Ruby one will be very familiar to you. All it does is define a few variables and then print them to the terminal. Now, look at the second snippet. Does it look familiar? The syntax is different but most likely it is apparent that similar action is occurring. An =
operator appears a few times, which likely jumps out at you as a set of assignment operations. Something called Console.WriteLine()
then gets called, which implies that values are being displayed on the terminal. And, there are some strings that appear to be using interpolation to build messages. At the conceptual level, none of this is particularly surprising—assignment, interpolation, and console output is something you are already familiar with from Ruby.
You may not need to know any C# to understand the second snippet, but there is certainly room to expand your mental models. For example, why is everything wrapped in a Main()
method? And what are these int
, double
, and string
keywords? Here is where the process of learning begins. You already know the general boundaries of what is happening (assignment, interpolation, output), now it’s time to learn the particulars:
Main()
: Some quick research will reveal that theMain()
method is the entry point where program execution occurs. Now you know that you’re going to need aMain()
method in all of your C# programs.- Variables: The first part of our C# snippet is clearly assignment of some sort. Given the nomenclature, you can probably guess that the
int
keyword indicates a variable containing an integer,double
a variable containing a double floating point number, andstring
a variable containing a string. Almost immediately, you have an idea that C#, unlike Ruby, must be statically typed since different data types require different variable declaration. Reading the documentation will confirm as much. Console.WriteLine()
: Finally, by running the program you will see thatConsole.WriteLine()
is outputting values to the console. From Ruby, you know thatputs
is a method on the global$stdout
object and when you look up the documentation forConsole.WriteLine()
you will see thatConsole
is a class in theSystem
namespace andWriteLine()
is a method defined in that class. Not only is this very similar toputs
, but it also tells you that C#, like Ruby, is object-oriented. Now you have another mental model you can use to draw parallels.
The above is a very simple example, but even this simple example provides some important insights. You have already learned that C# requires a defined entry point, is statically typed (unlike Ruby), and is object-oriented (like Ruby). You learned this first by using existing mental models about things like what variables and methods are, and then you expanded on those models by extending them to include concepts like static typing.
Look for Differences
As you start to examine how to read and write code in a new language, the first step is to see where things are the same, which provides a foundation for you to build on. After that, your next step is to see where things are different. Let’s go back to our Ruby-to-C# learning transition and try something a bit more complicated.
particles = ["electron", "proton", "neturon"]
particles.push("muon")
particles.push("photon")
particles.each do |particle|
puts particle
end
In this Ruby snippet, we define an array called particles
with a few strings in it and then we use Array#push
to add a few more strings and Array#each
to iterate over the array and print each of the strings to the terminal. But can we do the same thing in C#? After some quick Googling you will see that C# has typed arrays (the typing won’t be a surprise to you given what you learned earlier) and there is a method called SetValue
, which sounds a bit like push
but has a value and an index position as parameters. Your first attempt at recreating the Ruby snippet then might look like this:
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
string[] particles = new string[] { "electron", "proton", "neturon" };
particles.SetValue("muon", 3);
// Run-time exception (line 11): Index was outside the bounds of the array.
particles.SetValue("photon", 4);
foreach (string particle in particles)
{
Console.WriteLine(particle);
}
}
}
Unfortunately, this code produces a Run-time exception
when you try to use SetValue
to add a new value onto the array. A look at the documentation will tell you that arrays in C# are not dynamic and must be initialized either with all their values or with a length specification. Your second attempt to replicate the original Ruby code takes this into account and might then look like this:
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
string[] particles = new string[] { "electron", "proton", "neturon", null, null };
particles.SetValue("muon", 3);
particles.SetValue("photon", 4);
foreach (string particle in particles)
{
Console.WriteLine(particle);
}
}
}
This snippet does indeed replicate the functionality of the original Ruby snippet but only by the loose definition that they both output the same values to the terminal. If you examine the two snippets more closely, you will quickly see a problem: in the C# snippet you can have a maximum of 5 values in the particles array, whereas in the Ruby snippet you could add as many as you want. It would appear then that arrays in Ruby and C# have a key difference in that the former is dynamic in size and the latter is not. In C#, to truly replicate the Ruby snippet’s functionality, you would need something more like this:
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<String> particles = new List<String>();
particles.Add("electron");
particles.Add("proton");
particles.Add("neutron");
particles.Add("muon");
particles.Add("photon");
foreach (string particle in particles)
{
Console.WriteLine(particle);
}
}
}
Here, you are using a List
data structure to dynamically gather values together. By doing so, you can use C# to effectively replicate the original Ruby code, but more importantly, you have learned a key difference between the two languages. Although the use of the term “array” in both languages might have implied that they are the same, in actual usage they are quite different. This provides you with another useful point at which to expand your mental model of what an “array” is and how it operates. In C#, an array may or may not be the right data structure to use in places where you would have used one in Ruby, depending on whether dynamic resizing is important to you. Now you know to think about that ahead of time and plan your code accordingly.
Go Back to Core Principles
Exploring new languages by looking for similarities and differences with languages you already know is a great starting point; however, sometimes the best place to start is from language-agnostic principles. Earlier we intuited that C# is an object-oriented language, given that we used a built-in class and one of its methods, System.Console.WriteLine()
to execute an action. We can then reasonably assume that, like other object-oriented languages, C# has a mechanism to define a class and then instantiate objects from it. This is a core principle of object-oriented programming and we can therefore be virtually assured that our assumption is correct. To give ourselves a starting point, let’s see what this might look like in our known language, Ruby.
class Element
attr_accessor :name, :symbol, :number
def initialize(name, symbol, number)
self.name = name
self.symbol = symbol
self.number = number
end
def describe
puts "#{self.name} (#{self.symbol}) has atomic number #{self.number}."
end
end
hydrogen = Element.new("Hydrogen", "H", 1)
hydrogen.describe
In this snippet, we have a simple class, Element
, which has a constructor method to accept values and assign them to instantiated objects, a set of accessor methods for getting/setting values, and instance method that outputs those values. The core concepts here are the idea of a class, the idea of a constructor method, the idea of getters/setters, and the idea of an instance method. Going back to our understanding of what an object-oriented language is capable of, let’s see how to do the same thing in C#.
using System;
public class Program
{
public static void Main()
{
Element hydrogen = new Element("Hydrogen", "H", 1);
hydrogen.Describe();
}
public class Element
{
public string Name { get; set; }
public string Symbol { get; set; }
public int Number { get; set; }
public Element(string name, string symbol, int number)
{
this.Name = name;
this.Symbol = symbol;
this.Number = number;
}
public void Describe()
{
Console.WriteLine
(
"{0} ({1}) has atomic number {2}.",
this.Name, this.Symbol, this.Number
);
}
}
}
Looking at the C# version of this snippet, we can see that it’s really not so different from the Ruby version. We define a class, use a constructor to define how the class will instantiate objects, define getters/setters, and define an instance method to call on the objects we make. Certainly the two snippets look quite different, but not in a surprising way. The C# version uses this
to refer to the object that is being instantiated where as Ruby used self
. The C# version is typed at both the method and parameter levels, whereas the Ruby one is not. However, at the level of core principles, the two snippets are virtually identical.
To expand on this theme, we might consider the idea of inheritance. We know that inheritance and subclassing are key aspects of object-oriented programming so it’s not a leap to imagine that this as possible in C# just like it is in Ruby.
class Element
attr_accessor :name, :symbol, :number
def initialize(name, symbol, number)
self.name = name
self.symbol = symbol
self.number = number
end
def describe
puts "#{self.name} (#{self.symbol}) has atomic number #{self.number}."
end
end
class NobleGas < Element
attr_accessor :category, :type, :reactivity
def initialize(name, symbol, number)
super(name, symbol, number)
self.category = "gas"
self.type = "noble gas"
self.reactivity = "low"
end
def describe
puts "#{self.name} (#{self.symbol}; #{self.number}) is a #{self.category} " +
"of type #{self.type}. It has #{self.reactivity} reactivity."
end
end
argon = NobleGas.new("Argon", "Ar", 18)
argon.describe
In our Ruby version, we define a subclass, NobleGas
, which inherits from our Element
class, uses the super
keyword in its constructor to expand on its parent class’ constructor, and then overrides the instance method describe
to define new behavior. We can do the same thing in C#, albeit with different syntax:
using System;
public class Program
{
public static void Main()
{
NobleGas argon = new NobleGas("Argon", "Ar", 18);
argon.Describe();
}
public class Element
{
public string Name { get; set; }
public string Symbol { get; set; }
public int Number { get; set; }
public Element(string name, string symbol, int number)
{
this.Name = name;
this.Symbol = symbol;
this.Number = number;
}
public virtual void Describe()
{
Console.WriteLine
(
"{0} ({1}) has atomic number {2}.",
this.Name, this.Symbol, this.Number
);
}
}
public class NobleGas : Element
{
public string Category { get; set; }
public string Type { get; set; }
public string Reactivity { get; set; }
public NobleGas(string name, string symbol, int number) : base(name, symbol, number)
{
this.Category = "gas";
this.Type = "noble gas";
this.Reactivity = "low";
}
public override void Describe()
{
Console.WriteLine
(
"{0} ({1}; {2}) is a {3} of type {4}. It has {5} reactivity.",
this.Name, this.Symbol, this.Number,
this.Category, this.Type, this.Reactivity
);
}
}
}
At first glance, prior to learning anything about C#, this last snippet might have seemed intimidating. The syntax is unfamiliar, there are strange keywords, and the code is not organized in a way we are used to. However, when you look at the code from a level of core principles, it’s really not so different—it’s just a class definition, a set of methods and variables, and a way to instantiate and use objects.
TL;DR
Learning a new language can be incredibly intimidating if you approach it from scratch. However, most programming languages share a set of foundational principles that we can use to draw parallels, spot important differences, and apply across multiple languages. By examining a new language through your existing mental models, you can look for places where your mental model can remain intact, and for places where it needs updating. As you learn more languages over time, your mental models will have expanded to the point where they need very little updating and can be drawn upon to recognize a wide variety of language implementations.
I hope you have enjoyed this short overview of how to apply existing programming knowledge to your efforts to learn new things. So, next time you’re feeling intimidated by a learning task that is set before you, take time to think back to fundamentals and you will no doubt see that you already know much of what you need to learn.
Note: This article was originally published on Medium.