We master new programming languages, based on the already studied

Original author: Severin Perez
  • Transfer
Hello colleagues.



A snapshot of Jenny Marvin from Unsplash

Today, we have prepared for you a translation of an article about the fundamental similarities of many programming languages ​​using the example of Ruby and C #. We hope that the ideas of the distinguished Severin Peres will help many of you to start learning a new programming language as soon as possible, and things will be done with sense and pleasure.

What does not take away from the programmer - he never stops learning. You may have a favorite language, or a framework, or a library, but there is no doubt that you cannot do it with them alone. You may like JavaScript, but on a project where you are currently working, Python may be required. You may be skilled in Perl, but the code base in your company can be written in C ++. For a rookie developer, the idea of ​​learning a new language may seem daunting, especially if deadlines are coming. This is bad news. However, there is a good one: it is usually not so difficult to learn a new language. If you take the already existing mental models as a basis, then you will see that learning a new language is basically an extension of the existing knowledge, and not work from scratch.

What do they have like


Most programming languages ​​are based on the same set of key principles. The implementation is different, but there are hardly two such dissimilar languages ​​that it will not be possible to draw parallels between them. In order to learn and understand a new language, the most important thing is to identify how it looks like you already know, and then acquire new knowledge, expanding your ideas about it when / if necessary. Consider, for example, data types and variables. In every language there is a way to identify and store data - uniformly throughout the program. Therefore, when learning a new language, you first need to understand how variables are defined and used here. Take for example two different languages: interpreted Ruby with dynamic typing and compiled C # with 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}"

Similar example:

using System;
publicclassProgram
{
    publicstaticvoidMain()
    {
        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);
    }
}

Suppose you are an experienced Ruby developer and want to learn C #. Below are the code snippets, in one of which you can easily recognize Ruby. There you just need to define a few variables and output them to the console. Now pay attention to the second fragment. Do you know anything? The syntax is different, but there is no doubt that the second code works much like the first. The = operator is encountered several times, which is most certainly striking as the symbol of assignment operations. Then some is called Console.WriteLine(), implying that the values ​​will be output to the console. There are also several lines in which interpolation appears to be used to compose messages. Conceptually, there is nothing particularly surprising here - assignment, interpolation, output to the console, all these operations are already known to you for working with Ruby.

Perhaps you will understand the second code fragment without any knowledge of C #, but there is definitely room for expanding your mental model. For example, why is everything wrapped in a method Main()? What are these keywords int, doubleand string? This is where the training begins. You already understand in general what is happening here (assignment, interpolation, output), now is the time to move on to the details:

  • Main(): Having a little penetrated into the situation, we find out that the method Main()is the input point from which the program starts. Now we know that in all C # programs we need the Main () method.
  • Variables: in the first part of our fragment in C #, a certain assignment definitely happens. Given the nomenclature, you can probably guess that the keyword intmeans an integer variable, doublea double-precision number with a floating point, and stringa string variable. Almost immediately, you will guess that in C #, unlike Ruby, static typing of variables is required, since variables are declared differently for different data types. After reading the documentation, you will understand how different.
  • Console.WriteLine()A: Finally, running the program, you will see that it Console.WriteLine()displays the values ​​in the console. From Ruby, you know that putsis a method of a global object $stdout, and by checking the documentation for Console.WriteLine(), you will find out that Consoleis a class from the namespace Systemand WriteLine()a method defined in this class. This is not only very reminiscent puts, but also suggests that C #, like Ruby, is an object-oriented language. Here you have another mental model that will help to trace the new parallels.

The above example is very simple, but even a number of important conclusions can be drawn from it. You have already learned that the C # program requires a well-defined entry point, that this language is statically typed (unlike Ruby) and object-oriented (like Ruby). You figured it out because you already have an idea of ​​what variables and methods are, and then expanded these mental models, enriching them with the phenomenon of typification.

Search for differences


Starting to try on how to read and write code in a new language, first of all you need to find out what things are already known and can serve as a basis for learning. Next, go to the differences. Let's return to our transition from Ruby to C # and look at something more complicated.

particles = ["electron", "proton", "neturon"]
particles.push("muon")
particles.push("photon")
particles.each do|particle|
  puts particle
end

In this fragment in Ruby, we define an array called with particlesseveral lines in it, and then use Array#pushit to add a few more lines to it, and Array#eachto iterate through the array and output each individual line to the console. But how to do the same in C #? A little googling, we learn that there are typed arrays in C # (typing should no longer surprise you, considering what you learned earlier), and also there is the SetValue method, which slightly reminds push, but takes as parameters the value and position in the index. In this case, the first attempt to rewrite Ruby code in C # might look like this:

using System;
using System.Collections.Generic;
publicclassProgram
{
    publicstaticvoidMain()
    {
        string[] particles = newstring[] { "electron", "proton", "neturon" };
        particles.SetValue("muon", 3);
            // Исключение времени выполнения (строка 11): индекс выходит за пределы массива
        particles.SetValue("photon", 4);
        foreach (string particle in particles)
        {
            Console.WriteLine(particle);
        }
    }
}

Unfortunately, this code will give a runtime exception Run-time exceptionwhen you try to use SetValueto add a new value to the array. Again, we look at the documentation and find out that the arrays in C # are not dynamic, and must be initialized either with all the values ​​at once, or with an indication of the length. Again, trying to reproduce the Ruby code, consider this and get the following option:

using System;
using System.Collections.Generic;
publicclassProgram
{
    publicstaticvoidMain()
    {
        string[] particles = newstring[] { "electron", "proton", "neturon", null, null };
        particles.SetValue("muon", 3);
        particles.SetValue("photon", 4);
        foreach (string particle in particles)
        {
            Console.WriteLine(particle);
        }
    }
}

This fragment really reproduces all the functionality of the Ruby source code, but with a big stretch: it just displays the same values ​​to the console. If you take a closer look at both fragments, then the problem is quickly discovered: there can be no more than 5 values ​​in the particles array in the particles array in the particles array, whereas in the fragments in ruby ​​they are allowed as many as you want. Then it becomes clear that the arrays in Ruby and C # are fundamentally different: the first one has a dynamic size, and the second one does not. In order to properly reproduce the fragment functionality in Ruby in C #, we need the following code:

using System;
using System.Collections.Generic;
publicclassProgram
{
    publicstaticvoidMain()
    {
        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);
        }
    }
}

It uses a data structure Listthat allows you to dynamically collect values. In this case, we actually reproduce the original Ruby code in C #, but, more importantly, here we can appreciate the key difference between the two languages. Although, in both languages ​​the term “array” is used and it may seem that these arrays are one and the same, in practice they are quite different. Here you have one more thing that helps to expand the mental model, to more fully understand what an “array” is and how it works. In C #, an array as a data structure may or may not be suitable in situations where you would use arrays in Ruby; Speech about situations where dynamic resizing of an array is critical. Now you have to take care of this in advance and think through your code accordingly.

Returning to key principles


It is very convenient to start exploring new languages, exploring their similarities and differences in comparison with already known languages; however, in some cases it is better to start with universal principles. Above, we logically concluded that C # is an object-oriented language when we worked with the built-in class and one of its methods System.Console.WriteLine(), with which we performed the action. It is logical to assume that in C #, as in other object-oriented languages, there is a mechanism for defining a class and instantiating objects from it. This is a basic principle of object-oriented programming, so there is little doubt that our assumption is correct. First, let's look at how this operation might look like in the Ruby language we know.

classElementattr_accessor:name, :symbol, :numberdefinitialize(name, symbol, number)self.name = name
    self.symbol = symbol
    self.number = number
  enddefdescribe
    puts "#{self.name} (#{self.symbol}) has atomic number #{self.number}."endend
hydrogen = Element.new("Hydrogen", "H", 1)
hydrogen.describe

Here we have a simple class Elementin which there is a constructor method to accept values ​​and assign them to instantiated objects, a set of access methods for setting and retrieving values, and an instance method for outputting these values. In this case, the key concepts are the idea of ​​a class, the idea of ​​a constructor method, the idea of ​​getters / setters, and the idea of ​​an instance method. Returning to our ideas about what can be done in object-oriented languages, consider how to do the same in C #.

using System;
publicclassProgram
{
    publicstaticvoidMain()
    {
        Element hydrogen = new Element("Hydrogen", "H", 1);
        hydrogen.Describe();
    }
    publicclassElement
    {
        publicstring Name { get; set; }
        publicstring Symbol { get; set; }
        publicint Number { get; set; }
        publicElement(string name, string symbol, int number)
        {
            this.Name = name;
            this.Symbol = symbol;
            this.Number = number;
        }
        publicvoidDescribe()
        {
            Console.WriteLine
            (
                "{0} ({1}) has atomic number {2}.",
                this.Name, this.Symbol, this.Number
            );
        }
    }
}

Having studied this fragment in C #, we see that, in fact, it is not so different from the Ruby version. We define a class, use the constructor to specify how the class will instantiate objects, define getters / setters, and define an instance method that we will call in the created objects. Naturally, the two fragments are quite different in appearance, but not in a most unexpected way. In the C # version, we refer thisto the object being instantiated with, while in Ruby it is used for this self. The C # version is typed both at the method level and at the parameter level, but not in Ruby. However, at the level of key principles, both fragments are almost identical.

Developing this topic, we can consider the idea of ​​inheritance. Inheritance and subclassing are known to be key points in object-oriented programming, so it’s easy to understand that in C # this is done as well as in Ruby.

classElementattr_accessor:name, :symbol, :numberdefinitialize(name, symbol, number)self.name = name
    self.symbol = symbol
    self.number = number
  enddefdescribe
    puts "#{self.name} (#{self.symbol}) has atomic number #{self.number}."endendclassNobleGas < Elementattr_accessor:category, :type, :reactivitydefinitialize(name, symbol, number)super(name, symbol, number)
    self.category = "gas"self.type = "noble gas"self.reactivity = "low"enddefdescribe
    puts "#{self.name} (#{self.symbol}; #{self.number}) is a #{self.category} " +
         "of type #{self.type}. It has #{self.reactivity} reactivity."endend
argon = NobleGas.new("Argon", "Ar", 18)
argon.describe

In the Ruby version, we define a subclass NobleGasthat inherits from our class Element; its constructor uses the super keyword, which extends the constructor of the parent class and then overrides the instance method describeto define the new behavior. The same can be done in C #, but with a different syntax:

using System;
publicclassProgram
{
    publicstaticvoidMain()
    {
        NobleGas argon = new NobleGas("Argon", "Ar", 18);
        argon.Describe();
    }
    publicclassElement
    {
        publicstring Name { get; set; }
        publicstring Symbol { get; set; }
        publicint Number { get; set; }
        publicElement(string name, string symbol, int number)
        {
            this.Name = name;
            this.Symbol = symbol;
            this.Number = number;
        }
        publicvirtualvoidDescribe()
        {
            Console.WriteLine
            (
                "{0} ({1}) has atomic number {2}.",
                this.Name, this.Symbol, this.Number
            );
        }
    }
    publicclassNobleGas : Element
    {
        publicstring Category { get; set; }
        publicstring Type { get; set; }
        publicstring Reactivity { get; set; }
        publicNobleGas(string name, string symbol, int number) : base(name, symbol, number)
        {
            this.Category = "gas";
            this.Type = "noble gas";
            this.Reactivity = "low";
        }
        publicoverridevoidDescribe()
        {
            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, when we still did not know anything about C #, this last listing might have seemed intimidating. The syntax is unfamiliar, some strange keywords and code are not organized in the way we are used to. However, if we consider this code from the point of view of basic principles, the difference is not so significant: here we just have to define a class, a set of methods and variables, and a number of rules for instantiating and using objects.

TL; DR


Learning a new language can be hellishly difficult if you do it from scratch. However, most programming languages ​​are based on the same basic principles, between which it is easy to draw parallels, notice important differences and apply in many languages. Trying on a new language on already established mental models, you can find where it does not differ from the already studied languages, and where something really needs to be clarified. Studying with time more and more new languages, you expand your mental models, and such refinements require less and less - you recognize various implementations in various languages ​​from the sheet.

Also popular now: