Wir beherrschen neue Programmiersprachen und stützen uns auf das bereits Gelernte

Hallo Kollegen.



Jenny Marvin Schnappschuss von Unsplash

Heute haben wir 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 angesehenen Severin Peres vielen von Ihnen helfen werden, schnell eine neue Programmiersprache zu lernen, und die Dinge werden mit echtem Sinn und Vergnügen verlaufen.

Was dem Programmierer nicht wegnimmt - 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 nicht ausschließlich mit ihnen arbeiten können. Sie mögen vielleicht JavaScript, aber für das Projekt, an dem Sie gerade arbeiten, ist möglicherweise Python erforderlich. Sie sind vielleicht mit Perl vertraut, aber die Codebasis in Ihrem Unternehmen kann in C ++ geschrieben werden. Für einen Rookie-Entwickler kann die Idee, eine neue Sprache zu lernen, entmutigend erscheinen, insbesondere wenn Fristen kommen. Das sind schlechte Nachrichten. Es gibt jedoch eine gute: Das Erlernen einer neuen Sprache ist normalerweise nicht so schwierig. Wenn wir die vorhandenen mentalen Modelle als Grundlage nehmen, werden Sie sehen, dass das Erlernen einer neuen Sprache im Grunde eine Erweiterung des vorhandenen Wissens ist und nicht von Grund auf neu funktioniert.

Was haben sie gemeinsam?


Die meisten Programmiersprachen basieren im Wesentlichen auf denselben Schlüsselprinzipien. Die Implementierung ist unterschiedlich, aber es gibt kaum zwei Sprachen, die so unterschiedlich sind, dass es nicht möglich ist, Parallelen zwischen ihnen zu ziehen. Um eine neue Sprache zu lernen und zu verstehen, ist es am wichtigsten, zu identifizieren, wie es aussieht, als ob Sie sie bereits kennen, und dann neues Wissen zu erwerben, um Ihr Verständnis bei Bedarf zu erweitern. Betrachten Sie beispielsweise Datentypen und Variablen. In jeder Sprache gibt es eine Möglichkeit, Daten zu definieren und zu speichern - einheitlich im gesamten Programm. Wenn Sie eine neue Sprache lernen, müssen Sie daher zunächst verstehen, wie Variablen hier definiert und verwendet werden. Nehmen Sie als Beispiel zwei verschiedene Sprachen: Ruby mit dynamischer Typisierung interpretiert und C # mit statischer Typisierung kompiliert.

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

Ein ähnliches Beispiel:

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

Angenommen, Sie sind ein erfahrener Ruby-Entwickler und möchten C # lernen. Im Folgenden finden Sie Codeausschnitte, in denen Sie Ruby leicht erkennen können. Dort müssen Sie nur mehrere Variablen definieren und in der Konsole anzeigen. Achten Sie nun auf das zweite Fragment. Lernst du etwas Die Syntax ist unterschiedlich, aber es besteht kein Zweifel, dass der zweite Code ähnlich wie der erste funktioniert. Der Operator = wird mehrmals angetroffen, was wahrscheinlich als Symbol für Zuweisungsoperationen auffällt. Dann wird eine bestimmte Console.WriteLine() aufgerufen, was bedeutet, dass die Werte in der Konsole angezeigt werden. Es gibt hier auch einige Zeilen, die offenbar Interpolation zum Verfassen von Nachrichten verwenden. Konzeptionell gibt es hier nichts besonders Überraschendes - Zuweisung, Interpolation, Ausgabe an die Konsole, all diese Vorgänge sind Ihnen bereits bei der Arbeit mit Ruby bekannt.

Sie können das zweite Codefragment ohne C # -Kenntnisse verstehen, aber es gibt definitiv Möglichkeiten, Ihr mentales Modell zu erweitern. Warum wickelt sich beispielsweise jeder in die Main() -Methode ein? Was sind diese Schlüsselwörter int , double und string ? Damit beginnt das Training. Sie verstehen bereits allgemein, was hier passiert (Zuweisung, Interpolation, Ausgabe), jetzt ist es Zeit, zu den Details überzugehen:

  • Main() : Wenn wir etwas tiefer in die Situation eintauchen, stellen wir fest, dass die Main() -Methode der Einstiegspunkt 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 # tritt definitiv eine Art Zuordnung auf. Angesichts der Nomenklatur vermuten Sie wahrscheinlich, dass das Schlüsselwort int eine Ganzzahlvariable bedeutet, double eine Gleitkommazahl mit doppelter Genauigkeit und string eine Zeichenfolgenvariable ist. Fast sofort stellen Sie fest, 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 ist.
  • Console.WriteLine() : Console.WriteLine() Sie das Programm Console.WriteLine() , sehen Sie, dass Console.WriteLine() die Werte in der Konsole anzeigt. Aus Ruby wissen Sie, dass puts eine Methode des globalen $stdout Objekts ist. Wenn Sie sich die Dokumentation zu Console.WriteLine() ansehen, werden Sie feststellen, dass Console eine Klasse aus dem System Namespace ist und WriteLine() die hier definierte Methode ist Klassenzimmer. Dies sieht nicht nur sehr nach puts , sondern legt auch nahe, dass C # wie Ruby eine objektorientierte Sprache ist. Hier haben Sie ein anderes mentales Modell, mit dem Sie neue Parallelen aufspüren können.

Das obige Beispiel ist sehr einfach, aber es können sogar eine Reihe wichtiger Schlussfolgerungen daraus gezogen werden. Sie haben bereits erfahren, dass ein C # -Programm einen genau definierten Einstiegspunkt erfordert, dass diese Sprache statisch typisiert (im Gegensatz zu Ruby) und objektorientiert (wie Ruby) ist. Sie haben es herausgefunden, weil Sie sich bereits vorstellen, was Variablen und Methoden sind, und dann haben Sie diese mentalen Modelle erweitert und sie mit dem Typisierungsphänomen angereichert.

Suche nach Unterschieden


Beginnen Sie mit dem Lesen und Schreiben von Code in einer neuen Sprache. Als Erstes müssen Sie herausfinden, welche Dinge bereits bekannt sind und als Grundlage für das Lernen dienen können. Fahren Sie als nächstes mit den Unterschieden fort. Kehren wir zu unserem Übergang von Ruby zu C # zurück und schauen uns etwas Komplizierteres an.

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

In diesem Ruby-Fragment definieren wir ein Array namens particles , das mehrere Zeilen enthält, und verwenden dann Array#push , um ein paar weitere Zeilen hinzuzufügen, und Array#each , um jeweils über das Array zu iterieren und jede einzelne Zeile an die Konsole auszugeben. Aber wie geht das in C #? Ein wenig googeln, wir stellen fest, dass C # Arrays getippt hat (das Tippen sollte Sie nicht mehr überraschen, wenn man bedenkt, was Sie zuvor gelernt haben), und es gibt auch eine SetValue-Methode, die leicht dem push ähnelt, aber den Wert und die Position im Index als Parameter verwendet. In diesem Fall kann der erste Versuch, den Ruby-Code in C # neu zu schreiben, Folgendes bewirken:

 using System; using System.Collections.Generic; public class Program { public static void Main() { string[] particles = new string[] { "electron", "proton", "neturon" }; particles.SetValue("muon", 3); //    ( 11):      particles.SetValue("photon", 4); foreach (string particle in particles) { Console.WriteLine(particle); } } } 

Leider SetValue dieser Code eine Run-time exception wenn Sie versuchen, mit SetValue dem Array einen neuen Wert hinzuzufügen. Wir sehen uns erneut die Dokumentation an und stellen fest, dass Arrays in C # nicht dynamisch sind und entweder sofort mit allen Werten oder mit Angabe der Länge initialisiert werden sollten. Versuchen Sie erneut, den Ruby-Code zu reproduzieren, und erhalten Sie die folgende Option:

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

Dieses Snippet reproduziert wirklich alle Funktionen des Ruby-Quellcodes, jedoch mit einem großen Aufwand: Es zeigt der Konsole nur die gleichen Werte an. Wenn Sie beide Fragmente sorgfältig untersuchen, wird schnell ein Problem entdeckt: In einem Fragment in C # im Partikelarray dürfen nicht mehr als 5 Werte vorhanden sein, während in einem Fragment in Ruby so viele Werte zulässig sind, wie Sie möchten. Dann wird klar, dass sich die Arrays in Ruby und C # grundlegend unterscheiden: Ersteres hat eine dynamische Größe, letzteres nicht. Um die Ruby-Snippet-Funktionalität in C # ordnungsgemäß zu reproduzieren, benötigen Sie mehr von diesem Code:

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

Dies verwendet die Listendatenstruktur, um Werte dynamisch zu erfassen. In diesem Fall reproduzieren wir tatsächlich den ursprünglichen Ruby-Code in C #, aber was noch wichtiger ist, hier können wir den Hauptunterschied zwischen den beiden Sprachen erkennen. Obwohl in beiden Sprachen der Begriff „Array“ verwendet wird und es den Anschein haben mag, dass diese Arrays ein und dasselbe sind, sind sie in der Praxis sehr unterschiedlich. Hier ist noch eine Sache, die hilft, das mentale Modell zu erweitern, um besser zu verstehen, was ein „Array“ ist und wie es angeordnet ist. In C # kann ein Array als Datenstruktur in Situationen geeignet sein oder nicht, in denen Sie in Ruby auf Arrays zurückgreifen würden. Es handelt sich um Situationen, in denen die dynamische Größenänderung eines Arrays von entscheidender Bedeutung ist. Jetzt müssen Sie sich im Voraus darum kümmern und Ihren Code entsprechend durchdenken.

Zurück zu den wichtigsten Prinzipien


Es ist sehr praktisch, neue Sprachen zu lernen und deren Ähnlichkeiten und Unterschiede zu bereits bekannten Sprachen zu untersuchen. In einigen Fällen ist es jedoch ratsamer, mit universellen Prinzipien zu beginnen. Oben haben wir logischerweise festgestellt, dass C # eine objektorientierte Sprache ist, als wir mit einer integrierten Klasse und einer ihrer Methoden, System.Console.WriteLine() , gearbeitet haben, mit denen wir eine Aktion ausgeführt haben. Es ist logisch anzunehmen, dass es in C # wie in anderen objektorientierten Sprachen einen Mechanismus zum Definieren einer Klasse und zum Instanziieren von Objekten daraus gibt. Dies ist ein Grundprinzip der objektorientierten Programmierung, sodass Sie kaum Zweifel an der Richtigkeit unserer Annahme haben können. Schauen wir uns zunächst an, wie dieser Vorgang in der bekannten Ruby-Sprache aussehen könnte.

 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 

Hier haben wir eine einfache Element , in der es eine Konstruktormethode gibt, um Werte zu akzeptieren und instanziierten Objekten zuzuweisen, eine Reihe von Zugriffsmethoden zum Setzen und Empfangen von Werten sowie eine Instanzmethode zum Ausgeben dieser Werte. In diesem Fall sind die Schlüsselkonzepte die Idee einer Klasse, die Idee einer Konstruktormethode, die Idee von Gettern / Setzern und die Idee einer Instanzmethode. Zurück zu unseren Vorstellungen darüber, was in objektorientierten Sprachen getan werden kann, werden wir uns ansehen, wie Sie dasselbe in C # tun können.

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

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, indem wir mit dem Konstruktor angeben, wie die Klasse Objekte instanziiert, Getter / Setter definiert und die Instanzmethode bestimmt, die wir in den erstellten Objekten aufrufen. Natürlich sehen die beiden Fragmente sehr unterschiedlich aus, aber nicht auf unerwartete Weise. In der C # -Version verwenden wir this , um auf das instanziierte Objekt zu verweisen, während wir in Ruby dafür self . Die C # -Version wird sowohl auf Methodenebene als auch auf Parameterebene eingegeben, in Ruby jedoch nicht. 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. Es ist bekannt, dass Vererbung und Unterklasse die Schlüsselpunkte der objektorientierten Programmierung sind. Daher ist es leicht zu verstehen, dass dies in C # mit dem gleichen Erfolg wie in Ruby erfolgt.

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

Auf den ersten Blick, als wir noch nichts über C # wussten, könnte diese letzte Auflistung einschüchternd wirken. Die Syntax ist unbekannt, einige seltsame Schlüsselwörter und Code sind nicht mehr so ​​organisiert wie früher. Wenn wir diesen Code jedoch unter dem Gesichtspunkt der Grundprinzipien betrachten, ist der Unterschied nicht so bedeutend: Hier haben wir nur eine Klassendefinition, eine Reihe von Methoden und Variablen und eine Reihe von Regeln zum Instanziieren und Verwenden von Objekten.

TL; DR


Das Erlernen einer neuen Sprache kann höllisch schwierig sein, wenn Sie es von Grund auf neu machen. Die meisten Programmiersprachen basieren jedoch auf denselben Grundprinzipien, zwischen denen sich leicht Parallelen ziehen, wichtige Unterschiede feststellen und in vielen Sprachen anwenden lassen. Wenn Sie eine neue Sprache anhand bestehender mentaler Modelle ausprobieren, können Sie feststellen, wo sie sich nicht von den bereits untersuchten Sprachen unterscheidet und wo Sie wirklich etwas klären müssen. Wenn Sie im Laufe der Zeit immer mehr Sprachen lernen, erweitern Sie Ihre mentalen Modelle, und solche Verfeinerungen sind immer weniger erforderlich - Sie werden verschiedene Implementierungen in verschiedenen Sprachen auf dem Blatt erkennen.

Source: https://habr.com/ru/post/de422869/


All Articles