Dominamos nuevos lenguajes de programación, confiando en los ya aprendidos

Hola colegas



Instantánea de Jenny Marvin de Unsplash

Hoy hemos preparado para usted una traducción de un artículo sobre las similitudes fundamentales de muchos lenguajes de programación utilizando Ruby y C # como ejemplo. Esperamos que las ideas del respetado Severin Peres ayuden a muchos de ustedes a comenzar rápidamente a aprender un nuevo lenguaje de programación, y las cosas irán con verdadero sentido y placer.

Lo que no le quita al programador: nunca deja de aprender. Es posible que tenga un idioma favorito, o un marco, o una biblioteca, pero no hay duda de que no puede hacerlo exclusivamente con ellos. Puede que le guste JavaScript, pero el proyecto en el que está trabajando actualmente puede requerir Python. Puede ser experto en Perl, pero la base de código de su empresa puede escribirse en C ++. Para un desarrollador novato, la idea de aprender un nuevo idioma puede parecer desalentadora, especialmente si se acerca la fecha límite. Estas son malas noticias. Sin embargo, hay una buena: aprender un nuevo idioma generalmente no es tan difícil. Si tomamos como base los modelos mentales existentes, verá que aprender un nuevo idioma es básicamente una extensión del conocimiento existente y no funciona desde cero.

¿Qué tienen en común?


La mayoría de los lenguajes de programación se basan básicamente en el mismo conjunto de principios clave. La implementación es diferente, pero apenas hay dos lenguajes tan diferentes que no sea posible establecer paralelos entre ellos. Para aprender y comprender un nuevo idioma, lo más importante es identificar cómo se ve como si ya lo supiera, y luego adquirir nuevos conocimientos, ampliando su comprensión del mismo cuando sea necesario. Considere, por ejemplo, tipos de datos y variables. En cada idioma, hay una manera de definir y almacenar datos, de manera uniforme en todo el programa. Por lo tanto, al aprender un nuevo idioma, primero debe comprender cómo se definen y usan las variables aquí. Tome dos lenguajes diferentes como ejemplo: interpretó Ruby con escritura dinámica y compiló C # con escritura estática.

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

Un ejemplo similar:

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

Digamos que eres un desarrollador experimentado de Ruby y quieres aprender C #. Los siguientes son fragmentos de código, en uno de los cuales puede reconocer fácilmente a Ruby. Allí solo necesita definir varias variables y mostrarlas en la consola. Ahora presta atención al segundo fragmento. ¿Aprendes algo? La sintaxis es diferente, pero no hay duda de que el segundo código funciona de manera muy similar al primero. El operador = se encuentra varias veces, lo que probablemente llama la atención como símbolo de las operaciones de asignación. Luego se llama a un determinado Console.WriteLine() , lo que implica que los valores se mostrarán en la consola. También hay algunas líneas aquí que parecen usar la interpolación para componer mensajes. Conceptualmente, no hay nada particularmente sorprendente aquí: asignación, interpolación, salida a la consola, todas estas operaciones ya las conoce al trabajar con Ruby.

Puede comprender el segundo fragmento de código sin ningún conocimiento de C #, pero definitivamente hay margen para expandir su modelo mental. Por ejemplo, ¿por qué todos se envuelven en el método Main() ? ¿Cuáles son estas palabras clave int , double y string ? Con esto, comienza el entrenamiento. Ya comprende en términos generales lo que está sucediendo aquí (asignación, interpolación, salida), ahora es el momento de pasar a los detalles:

  • Main() : teniendo un poco más de profundidad en la situación, descubrimos que el método Main() es el punto de entrada desde el cual se inicia el programa. Ahora sabemos que en todos los programas de C # necesitamos el método Main ().
  • Variables: en la primera parte de nuestro fragmento en C # definitivamente ocurre algún tipo de asignación. Dada la nomenclatura, probablemente adivine que la palabra clave int significa una variable entera, double es un número de coma flotante de doble precisión y string es una variable de cadena. Casi de inmediato, se da cuenta de que en C #, a diferencia de Ruby, se requiere un tipo estático de variables, ya que las variables se declaran de manera diferente para los diferentes tipos de datos. Después de leer la documentación, comprenderá lo diferente que es.
  • Console.WriteLine() : Finalmente, al ejecutar el programa, verá que Console.WriteLine() muestra los valores en la consola. De Ruby, sabe que puts es un método del objeto global $stdout , y si mira la documentación de Console.WriteLine() , encontrará que Console es una clase del espacio de nombres del System , y WriteLine() es el método definido en este aula Esto no solo se parece mucho a puts , sino que también sugiere que C #, como Ruby, es un lenguaje orientado a objetos. Aquí tienes otro modelo mental que te ayudará a trazar nuevos paralelos.

El ejemplo anterior es muy simple, pero incluso se pueden extraer varias conclusiones importantes de él. Ya aprendió que un programa C # requiere un punto de entrada bien definido, que este lenguaje está estáticamente tipado (a diferencia de Ruby) y orientado a objetos (como Ruby). Lo descubriste, porque ya te imaginas qué son las variables y los métodos, y luego expandiste estos modelos mentales, enriqueciéndolos con el fenómeno de tipificación.

Buscar diferencias


Comenzando a probar cómo leer y escribir código en un nuevo idioma, lo primero que necesita saber es qué cosas ya se conocen y pueden servir como base para el aprendizaje. Luego, pasa a las diferencias. Volvamos a nuestra transición de Ruby a C # y veamos algo más complicado.

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

En este fragmento de Ruby, definimos una matriz llamada particles , que contendrá varias líneas, y luego usamos Array#push para agregarle algunas líneas más, y Array#each para iterar sobre la matriz y enviar cada línea individual a la consola. Pero, ¿cómo hacer lo mismo en C #? Un poco en Google, descubrimos que C # ha escrito matrices (escribir no debería sorprenderlo más, teniendo en cuenta lo que aprendió anteriormente), y también hay un método SetValue que se parece un poco a push , pero toma el valor y la posición en el índice como parámetros. En este caso, el primer intento de reescribir el código Ruby en C # puede resultar en esto:

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

Desafortunadamente, este código arrojará una Run-time exception cuando intente usar SetValue para agregar un nuevo valor a la matriz. Una vez más, observamos la documentación y descubrimos que las matrices en C # no son dinámicas y deben inicializarse inmediatamente con todos los valores o con una indicación de la longitud. Una vez más, tratando de reproducir el código Ruby, considere esto y obtenga la siguiente opción:

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

Este fragmento realmente reproduce toda la funcionalidad del código fuente de Ruby, pero con una extensión: solo muestra los mismos valores en la consola. Si examina cuidadosamente ambos fragmentos, se descubre rápidamente un problema: en un fragmento en C # en la matriz de partículas no puede haber más de 5 valores, mientras que en un fragmento en Ruby se les permite tantos como desee. Entonces queda claro que las matrices en Ruby y C # son fundamentalmente diferentes: la primera tiene un tamaño dinámico, mientras que la segunda no. Para reproducir correctamente la funcionalidad del fragmento de Ruby en C #, necesita más de este código:

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

Esto utiliza la estructura de datos de List para recopilar valores dinámicamente. En este caso, en realidad reproducimos el código Ruby original en C #, pero, lo que es más importante, aquí podemos apreciar la diferencia clave entre los dos idiomas. Aunque en ambos idiomas se usa el término "matriz" y puede parecer que estas matrices son una y la misma, en la práctica son bastante diferentes. Aquí hay una cosa más que ayuda a expandir el modelo mental, para comprender mejor qué es un "conjunto" y cómo está organizado. En C #, una matriz como estructura de datos puede o no ser adecuada en situaciones en las que en Ruby recurriría a matrices; Estamos hablando de situaciones en las que el cambio de tamaño dinámico de una matriz es crítico. Ahora tiene que ocuparse de esto por adelantado y pensar en su código en consecuencia.

Volviendo a los principios clave


Es muy conveniente comenzar a aprender nuevos idiomas, explorando sus similitudes y diferencias en comparación con los idiomas ya conocidos; sin embargo, en algunos casos es más recomendable comenzar con principios universales. Arriba, concluimos lógicamente que C # era un lenguaje orientado a objetos cuando trabajamos con una clase incorporada y uno de sus métodos, System.Console.WriteLine() , con el que realizamos una acción. Es lógico suponer que en C #, como en otros lenguajes orientados a objetos, existe un mecanismo para definir una clase e instanciar objetos a partir de ella. Este es un principio básico de la programación orientada a objetos, por lo que puede tener pocas dudas sobre la exactitud de nuestra suposición. Primero, veamos cómo podría verse esta operación en el lenguaje familiar de 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 

Aquí tenemos una clase de Element simple, en la que hay un método de constructor para aceptar valores y asignarlos a objetos instanciados, un conjunto de métodos de acceso para establecer y recibir valores, y también un método de instancia para generar estos valores. En este caso, los conceptos clave son la idea de una clase, la idea de un método de construcción, la idea de captadores / establecedores y la idea de un método de instancia. Volviendo a nuestras ideas sobre lo que se puede hacer en lenguajes orientados a objetos, veremos cómo hacer lo mismo en 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 ); } } } 

Después de estudiar este fragmento en C #, vemos que, de hecho, no es tan diferente de la versión de Ruby. Definimos la clase, utilizando el constructor, especificamos cómo la clase instanciará los objetos, definiremos getters / setters y determinaremos el método de instancia que llamaremos en los objetos creados. Naturalmente, los dos fragmentos son bastante diferentes en apariencia, pero no de la manera más inesperada. En la versión de C #, usamos this para referirnos al objeto instanciado, mientras que en Ruby usamos self para esto. La versión de C # se escribe tanto a nivel de método como a nivel de parámetro, mientras que en Ruby no. Sin embargo, a nivel de principios clave, ambos fragmentos son casi idénticos.

Al desarrollar este tema, podemos considerar la idea de herencia. Se sabe que la herencia y las subclases son los puntos clave de la programación orientada a objetos, por lo que es fácil entender que en C # esto se hace con el mismo éxito que en 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 

En la versión Ruby, definimos una subclase de NobleGas que hereda de nuestra clase Element ; su constructor usa la palabra clave super, que extiende el constructor de la clase padre y luego anula el método de instancia de describe para definir un nuevo comportamiento. Lo mismo se puede hacer en C #, pero con una sintaxis diferente:

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

A primera vista, cuando todavía no sabíamos nada sobre C #, esta última lista podría parecer intimidante. La sintaxis no es familiar, algunas palabras clave y códigos extraños no están organizados como solíamos hacerlo. Sin embargo, si consideramos este código desde el punto de vista de los principios básicos, la diferencia no es tan significativa: aquí solo tenemos una definición de clase, un conjunto de métodos y variables y una serie de reglas para crear instancias y usar objetos.

TL; DR


Aprender un nuevo idioma puede ser terriblemente difícil si lo haces desde cero. Sin embargo, la mayoría de los lenguajes de programación se basan en los mismos principios básicos, entre los cuales es fácil establecer paralelos, notar diferencias importantes y aplicar en muchos lenguajes. Al probar un nuevo idioma en los modelos mentales existentes, puede encontrar dónde no difiere de los idiomas ya estudiados y dónde realmente necesita aclarar algo. Al estudiar más y más idiomas a lo largo del tiempo, expande sus modelos mentales, y tales refinamientos se requieren cada vez menos: reconocerá varias implementaciones en varios idiomas de la hoja.

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


All Articles