Wir beherrschen neue Programmiersprachen, basierend auf den bereits untersuchten

Ursprünglicher Autor: Severin Perez
  • Übersetzung
Hallo Kollegen.



Eine Momentaufnahme von Jenny Marvin aus Unsplash

Today hat für Sie eine Übersetzung eines Artikels über die grundlegenden Ähnlichkeiten vieler Programmiersprachen am Beispiel von Ruby und C # vorbereitet. Wir hoffen, dass die Ideen des ausgezeichneten Severin Peres vielen von Ihnen helfen werden, so schnell wie möglich eine neue Programmiersprache zu lernen, und die Dinge werden mit Sinn und Vergnügen erledigt.

Was dem Programmierer nichts anhaben kann - er hört nie auf zu lernen. Möglicherweise haben Sie eine Lieblingssprache, ein Framework oder eine Bibliothek, aber es besteht kein Zweifel, dass Sie dies nicht alleine tun können. Sie mögen JavaScript, aber in einem Projekt, in dem Sie gerade arbeiten, ist Python möglicherweise erforderlich. Sie sind vielleicht in Perl erfahren, aber die Codebasis in Ihrem Unternehmen kann in C ++ geschrieben werden. Für Neulinge-Entwickler mag die Idee, eine neue Sprache zu lernen, entmutigend erscheinen, insbesondere wenn Fristen eintreffen. Das sind schlechte Nachrichten. Es gibt jedoch eine gute: Es ist normalerweise nicht so schwierig, eine neue Sprache zu lernen. Wenn Sie sich auf die bereits vorhandenen Denkmodelle stützen, werden Sie feststellen, dass das Erlernen einer neuen Sprache im Wesentlichen eine Erweiterung des vorhandenen Wissens ist und nicht von Grund auf funktioniert.

Was haben sie gern?


Die meisten Programmiersprachen basieren auf denselben Grundprinzipien. Die Implementierung ist unterschiedlich, aber es gibt kaum zwei so unterschiedliche Sprachen, dass es nicht möglich ist, Parallelen zwischen ihnen zu ziehen. Um eine neue Sprache zu lernen und zu verstehen, ist es das Wichtigste, herauszufinden, wie es aussieht, als wüssten Sie es bereits. Dann erwerben Sie neues Wissen und erweitern gegebenenfalls Ihre Ideen darüber. Betrachten Sie zum Beispiel Datentypen und Variablen. In jeder Sprache gibt es eine Möglichkeit, Daten zu identifizieren und zu speichern - einheitlich im gesamten Programm. Wenn Sie also eine neue Sprache lernen, müssen Sie zunächst verstehen, wie Variablen hier definiert und verwendet werden. Nehmen Sie zum Beispiel zwei verschiedene Sprachen: Interpretierte Ruby mit dynamischer Typisierung und kompiliertes C # mit statischer Typisierung.

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}"

Ähnliches Beispiel:

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);
    }
}

Angenommen, Sie sind ein erfahrener Ruby-Entwickler und möchten C # lernen. Nachfolgend finden Sie die Codeausschnitte, in denen Sie Ruby leicht erkennen können. Dort müssen Sie nur einige Variablen definieren und an die Konsole ausgeben. Achten Sie nun auf das zweite Fragment. Weißt du was Die Syntax ist unterschiedlich, aber der zweite Code funktioniert zweifellos ähnlich wie der erste. Der Operator = wird mehrmals vorgefunden, was als Symbol für Zuweisungsoperationen auffallend ist. Dann werden einige aufgerufen Console.WriteLine(), was bedeutet, dass die Werte an die Konsole ausgegeben werden. Es gibt auch mehrere Zeilen, in denen die Interpolation zum Erstellen von Nachrichten verwendet wird. Konzeptionell ist hier nichts besonders überraschend - Zuweisung, Interpolation, Ausgabe an die Konsole, all diese Vorgänge sind Ihnen bereits für die Arbeit mit Ruby bekannt.

Vielleicht verstehen Sie das zweite Codefragment ohne Kenntnis von C #, aber es besteht definitiv Raum für die Erweiterung Ihres Denkmodells. Warum ist beispielsweise alles in eine Methode eingepackt Main()? Welche Art von Schlüsselwörtern int, doubleund string? Hier beginnt das Training. Sie verstehen bereits allgemein, was hier passiert (Zuweisung, Interpolation, Ausgabe), jetzt ist es an der Zeit, zu den Details zu gelangen:

  • Main(): Nachdem wir ein wenig in die Situation eingedrungen sind, stellen wir fest, dass die Methode Main()der Eingabepunkt ist, von dem aus das Programm startet. Jetzt wissen wir, dass wir in allen C # -Programmen die Main () - Methode benötigen.
  • Variablen: Im ersten Teil unseres Fragments in C # geschieht definitiv eine bestimmte Zuweisung. In Anbetracht der Nomenklatur können Sie wahrscheinlich vermuten, dass das Schlüsselwort inteine Ganzzahlvariable, doubleeine Zahl mit doppelter Genauigkeit mit einem Gleitkommawert und stringeine Stringvariable bedeutet. Fast sofort werden Sie vermuten, dass in C # im Gegensatz zu Ruby eine statische Typisierung von Variablen erforderlich ist, da Variablen für verschiedene Datentypen unterschiedlich deklariert werden. Nachdem Sie die Dokumentation gelesen haben, werden Sie verstehen, wie unterschiedlich sie sind.
  • Console.WriteLine()A: Wenn Sie das Programm Console.WriteLine()ausführen, werden die Werte in der Konsole angezeigt. Von Ruby wissen Sie, dass putses sich um eine Methode eines globalen Objekts $stdouthandelt. Wenn Sie in der Dokumentation nachsehen Console.WriteLine(), werden Sie feststellen, dass Consolees sich um eine Klasse aus dem Namespace Systemund WriteLine()eine in dieser Klasse definierte Methode handelt. Dies erinnert nicht nur sehr stark puts, sondern legt auch nahe, dass C # wie Ruby eine objektorientierte Sprache ist. Hier haben Sie ein anderes Denkmodell, das helfen wird, die neuen Parallelen aufzuspüren.

Das obige Beispiel ist sehr einfach, aber es lassen sich auch einige wichtige Schlussfolgerungen ziehen. Sie haben bereits gelernt, dass das C # -Programm einen genau definierten Einstiegspunkt erfordert, dass diese Sprache statisch (im Gegensatz zu Ruby) und objektorientiert (wie Ruby) ist. Sie haben es herausgefunden, weil Sie bereits eine Vorstellung davon haben, welche Variablen und Methoden es sind, und dann diese mentalen Modelle erweitert und mit dem Phänomen der Typisierung bereichert.

Suche nach Unterschieden


Um zu versuchen, Code in einer neuen Sprache zu lesen und zu schreiben, müssen Sie zunächst herausfinden, welche Dinge bereits bekannt sind und als Grundlage für das Lernen dienen können. Als nächstes gehen Sie zu den Unterschieden. Kehren wir zu unserem Übergang von Ruby zu C # zurück und betrachten etwas komplizierteres.

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

In diesem Fragment in Ruby definieren wir ein Array, das mit particlesmehreren Zeilen darin aufgerufen wird, und Array#pushfügen dann einige weitere Zeilen hinzu, Array#eachum das Array zu durchlaufen und jede einzelne Zeile an die Konsole auszugeben. Aber wie mache ich dasselbe in C #? Ein wenig googeln wir, dass in C # typisierte Arrays vorhanden sind (das Eingeben sollte Sie angesichts des zuvor Gelernten nicht länger überraschen), und es gibt auch die SetValue-Methode, die etwas erinnert push, aber als Parameter den Wert und die Position im Index verwendet. In diesem Fall könnte der erste Versuch, Ruby-Code in C # neu zu schreiben, folgendermaßen aussehen:

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);
        }
    }
}

Leider gibt dieser Code eine Laufzeitausnahme aus, Run-time exceptionwenn Sie versuchen, SetValueeinen neuen Wert zum Array hinzuzufügen. Wir betrachten die Dokumentation erneut und stellen fest, dass die Arrays in C # nicht dynamisch sind und entweder mit allen Werten auf einmal oder mit Angabe der Länge initialisiert werden müssen. Versuchen Sie erneut, den Ruby-Code zu reproduzieren, und beachten Sie die folgende 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);
        }
    }
}

Dieses Fragment reproduziert wirklich die gesamte Funktionalität des Ruby-Quellcodes, aber mit einer großen Ausdehnung: Es zeigt nur die gleichen Werte auf der Konsole an. Wenn Sie sich beide Fragmente genauer ansehen, wird das Problem schnell erkannt: Es können nicht mehr als 5 Werte im Particle-Array im Particle-Array im Particle-Array vorhanden sein, wohingegen in Ruby-Fragmenten beliebig viele zulässig sind. Dann wird klar, dass sich die Arrays in Ruby und C # grundlegend unterscheiden: Das erste hat eine dynamische Größe und das zweite nicht. Um die Fragment-Funktionalität in Ruby in C # ordnungsgemäß zu reproduzieren, benötigen wir den folgenden 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);
        }
    }
}

Es verwendet eine Datenstruktur List, mit der Sie Werte dynamisch erfassen können. In diesem Fall reproduzieren wir den ursprünglichen Ruby-Code tatsächlich in C #, aber noch wichtiger ist, dass wir den Unterschied zwischen den beiden Sprachen erkennen können. Obwohl in beiden Sprachen der Begriff „Array“ verwendet wird und es so aussieht, als seien diese Arrays identisch, sind sie in der Praxis recht unterschiedlich. Hier haben Sie noch eine weitere Möglichkeit, das mentale Modell zu erweitern, um besser zu verstehen, was ein „Array“ ist und wie es funktioniert. In C # kann ein Array als Datenstruktur in Situationen geeignet sein, in denen Sie Arrays in Ruby verwenden würden. Rede über Situationen, in denen die dynamische Größenänderung eines Arrays kritisch ist. Jetzt müssen Sie sich vorher darum kümmern und Ihren Code entsprechend durchdenken.

Rückkehr zu den wichtigsten Prinzipien


Es ist sehr praktisch, neue Sprachen zu erkunden und ihre Ähnlichkeiten und Unterschiede im Vergleich zu bereits bekannten Sprachen zu untersuchen. In einigen Fällen ist es jedoch besser, mit universellen Prinzipien zu beginnen. Oben haben wir logisch festgestellt, dass C # eine objektorientierte Sprache ist, als wir mit der eingebauten Klasse und einer ihrer Methoden gearbeitet haben System.Console.WriteLine(), mit der wir die Aktion ausgeführt haben. Es ist logisch anzunehmen, dass es in C # wie in anderen objektorientierten Sprachen einen Mechanismus gibt, um eine Klasse zu definieren und Objekte daraus zu instanziieren. Dies ist ein Grundprinzip der objektorientierten Programmierung, daher besteht wenig Zweifel daran, dass unsere Annahme richtig ist. Sehen wir uns zunächst an, wie diese Operation in der uns bekannten Sprache Ruby aussehen könnte.

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

Hier haben wir eine einfache Klasse, Elementin der es eine Konstruktormethode gibt, um Werte zu akzeptieren und sie instantiierten Objekten zuzuweisen, eine Reihe von Zugriffsmethoden zum Setzen und Abrufen von Werten und eine Instanzmethode zum Ausgeben dieser Werte. In diesem Fall sind die Schlüsselbegriffe die Idee einer Klasse, die Idee einer Konstruktormethode, die Idee der Getter / Setter und die Idee einer Instanzmethode. Wenn wir zu unseren Vorstellungen zurückkehren, was in objektorientierten Sprachen möglich ist, überlegen Sie, wie Sie dies in C # tun können.

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
            );
        }
    }
}

Nachdem wir dieses Fragment in C # untersucht haben, sehen wir, dass es sich tatsächlich nicht so sehr von der Ruby-Version unterscheidet. Wir definieren eine Klasse, verwenden den Konstruktor, um anzugeben, wie die Klasse Objekte instanziiert, Getters / Setter definiert und eine Instanzmethode definiert, die in den erstellten Objekten aufgerufen wird. Natürlich sind die beiden Fragmente recht unterschiedlich, aber nicht auf unerwartete Weise. In der C # -Version beziehen wir uns auf thisdas Objekt, mit dem instanziiert wird, während es in Ruby dafür verwendet wird self. Die C # -Version wird sowohl auf Methodenebene als auch auf Parameterebene eingegeben, nicht jedoch in Ruby. Auf der Ebene der Schlüsselprinzipien sind beide Fragmente jedoch nahezu identisch.

Bei der Entwicklung dieses Themas können wir die Idee der Vererbung berücksichtigen. Vererbung und Unterklassifizierung sind bekanntermaßen Schlüsselpunkte in der objektorientierten Programmierung. Daher ist es leicht zu verstehen, dass dies in C # genauso wie in Ruby geschieht.

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 der Ruby-Version definieren wir eine Unterklasse NobleGas, die von unserer Klasse erbt Element. Sein Konstruktor verwendet das Schlüsselwort super, das den Konstruktor der übergeordneten Klasse erweitert und dann die Instanzmethode überschreibt describe, um das neue Verhalten zu definieren. Dasselbe kann in C # gemacht werden, jedoch mit einer anderen 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
            );
        }
    }
}

Auf den ersten Blick, als wir noch nichts über C # wussten, war diese letzte Auflistung möglicherweise einschüchternd. Die Syntax ist nicht vertraut, einige seltsame Schlüsselwörter und Code sind nicht so angeordnet, wie wir es gewohnt sind. Wenn wir diesen Code aus der Sicht der grundlegenden Prinzipien betrachten, ist der Unterschied jedoch nicht so signifikant: Hier haben wir nur eine Klassendefinition, eine Reihe von Methoden und Variablen und eine Reihe von Regeln für die Instantiierung und Verwendung von Objekten.

TL; DR


Das Erlernen einer neuen Sprache kann höllisch schwierig sein, wenn Sie es von Grund auf tun. Die meisten Programmiersprachen basieren jedoch auf den gleichen Grundprinzipien, zwischen denen es leicht ist, Parallelen zu ziehen, wichtige Unterschiede zu bemerken und in vielen Sprachen anzuwenden. Wenn Sie eine neue Sprache anhand bereits etablierter Denkmodelle ausprobieren, können Sie feststellen, wo sie sich nicht von den bereits gelernten Sprachen unterscheidet und wo wirklich etwas geklärt werden muss. Wenn Sie mit der Zeit immer mehr neue Sprachen studieren, erweitern Sie Ihre Denkmodelle, und für solche Verfeinerungen ist immer weniger erforderlich - Sie erkennen verschiedene Implementierungen in verschiedenen Sprachen aus dem Arbeitsblatt.

Jetzt auch beliebt: