De un traductor: Este artículo es una traducción de
material escrito por el programador
Alastair Paragas de Apple y ha trabajado con lenguajes de programación como Javascript, Python, PHP, Java, Scala, Haskell, Swift y Rust. Alastair comparte sus pensamientos sobre el tema de elegir y aprender "su" idioma, porque este tema es relevante tanto para principiantes como para profesionales que desean elegir un nuevo juego de herramientas.
Ya sea que aprenda un lenguaje de programación por motivos de empleo o capacitación avanzada, o sea pura afición, tarde o temprano tendrá que elegir entre ellos. Como hacerlo La pregunta no es simple, pero la respuesta es: miles de programadores lo hacen todos los días. Para facilitar su tarea, vale la pena seguir algunos principios.
Skillbox recomienda: Curso práctico "Profession Web Developer" .
Le recordamos: para todos los lectores de Habr: un descuento de 10.000 rublos al registrarse en cualquier curso de Skillbox de acuerdo con el código promocional de Habr
.
Indicadores comparativos
Niveles de abstracción.Si está muy generalizado, podemos decir que los lenguajes de programación modernos se dividen en tres tipos:
- "Rápido", que se utilizan para crear rápidamente aplicaciones o sus prototipos.
- "Infraestructura", que ayuda a optimizar o modificar partes de una aplicación ya escrita para aumentar su productividad.
- Los llamados lenguajes de programación del sistema, cuyo uso le permite obtener un control completo sobre la memoria del dispositivo.
Por supuesto, la separación real de los tipos entre los lenguajes de programación es menos estricta: existen variantes híbridas intermedias de varios tipos.
Si hablamos de aprender idiomas, entonces primero debe probar el primer tipo: idiomas "rápidos": le permiten ver inmediatamente el resultado del trabajo y aprender de sus propios errores. Estos son principalmente PHP, Javascript, Ruby y Python. El umbral de entrada aquí es mínimo, y puede aprender los conceptos básicos en poco tiempo. Estos lenguajes tienen bibliotecas estándar que le permiten agregar una gran cantidad de funciones a la aplicación, y el rango de sus capacidades es bastante amplio.
from concurrent.futures import ThreadPoolExecutor from http.client import HTTPException from urllib import request from typing import Union, Dict, Any, List def get_request_task(url: str) -> Union[List[Dict[str, Any]], None]: try: contents = None with request.urlopen(url) as response: contents = response.read() return contents except HTTPException: return None with ThreadPoolExecutor() as executor: for result in executor.map(get_request_task, [ "https://jsonplaceholder.typicode.com/posts", "https://jsonplaceholder.typicode.com/comments", "https://jsonplaceholder.typicode.com/albums" ]): if result is None: print("Something terrible has happened!") else: print(result)
Implementación de solicitudes HTTP multiproceso en Python con escritura estática. Multithreading proporciona la posibilidad de alternar tres tareas (llamémoslas tareas A, B y C). Mientras que una tarea (por ejemplo, la tarea A) realiza alguna operación relacionada con E / S (y, por lo tanto, no realiza ningún trabajo de cálculo), otras tareas se realizan simultáneamente con ella.En cuanto a los lenguajes de "infraestructura", estos son Java, Kotlin, Scala, Clojure, así como GoLang, Swift y Haskell. Puede llamarlos convenientes con un estiramiento, pero le permiten crear aplicaciones productivas. Las complejidades incluyen menos elementos listos para usar, sintaxis precisa, etc. Estos idiomas son buenos porque le permiten ajustar la aplicación. Si necesita velocidad, intente escribir una aplicación en uno de ellos.
import Foundation import Dispatch func getRequestTask(url: String, dispatchGroup: DispatchGroup) { dispatchGroup.enter() let request = URLRequest(url: URL(string: url)!) let task = URLSession(configuration: URLSessionConfiguration.default).dataTask( with: request, completionHandler: { (data, response, error) in if let data = data { if let dataAsString = String(data: data, encoding: .utf8) { print(dataAsString) dispatchGroup.leave() return } } print("Something terrible has happened!") dispatchGroup.leave() } ) task.resume() } let requestDispatchGroup = DispatchGroup() for url in [ "https://jsonplaceholder.typicode.com/posts", "https://jsonplaceholder.typicode.com/comments", "https://jsonplaceholder.typicode.com/albums" ] { getRequestTask(url: url, dispatchGroup: requestDispatchGroup) } requestDispatchGroup.wait()
Un problema similar ya se ha resuelto anteriormente usando Python. Ahora en el negocio - Swift.Lenguajes de programación del sistema: C, C ++, Rust. Proporcionan el máximo control sobre la aplicación, incluida la gestión de memoria. Además, estos lenguajes son excelentes para programar microcontroladores, computadoras con arquitectura de procesador personalizada y otros sistemas. Los idiomas de bajo nivel siguen siendo importantes y lo más probable es que sigan siendo relevantes en el futuro cercano.
FuncionalidadComo saben, los idiomas son un medio de "comunicación" entre una computadora y un programador. Para que esta comunicación sea fluida, vale la pena estudiar la sintaxis del lenguaje en detalle. En particular, un especialista debe conocer las estructuras de datos más utilizadas y comprender cómo modificar ciertos elementos de su aplicación.
module Main where import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.Resource (runResourceT) import Data.Conduit (($$+-), ($=+), runConduit) import Data.Conduit.List (mapM_, map, filter, catMaybes) import Data.Text (unpack) import Data.Maybe (fromJust) import Web.Twitter.Types (StreamingAPI(SStatus, SRetweetedStatus) , Status(Status), statusText, statusLang , RetweetedStatus(RetweetedStatus), rsRetweetedStatus ) import Web.Twitter.Conduit.Stream (stream)
Haskell es un lenguaje de programación funcional estricto. Puede verificar las estructuras de datos entrantes y trabajar con ellas si cumplen ciertos requisitos.
Tiempo de ejecución : necesita saber cómo funcionará su aplicación en varios sistemas. ¿Se necesita un intérprete de lenguaje (por ejemplo, Python, NodeJS, PHP)? ¿Se genera un binario dependiente del sistema (por ejemplo, Swift y GoLang)? ¿Utiliza el idioma seleccionado una combinación de las opciones primera y segunda, por ejemplo, la aplicación se compila y se inicia en alguna máquina virtual (Java, Scala, Clojure)?
Por cierto, en el camino hacia la excelencia, es muy recomendable estudiar y comenzar a usar Docker plus para asegurarse de comprender los principios de administración de Linux.
Bibliotecas : cada idioma es adecuado en ciertas situaciones. Por ejemplo, Java cumple con muchos de los requisitos de orquestación y logística de red, incluido el soporte de bases de datos a través de la estandarización de la interfaz JDBC y proyectos como los que están bajo el apoyo de la Fundación Apache. Lo mismo ocurre con Python, es ideal para el análisis de datos y los cálculos estadísticos, así como con Haskell con sus gramáticas, expresiones regulares y compiladores. La popularidad del lenguaje y el tamaño de su comunidad son dos argumentos más que hablan a favor del uso de una determinada herramienta de programación en su proyecto. Si la comunidad es pequeña, no debe contar con la asistencia de ambulancia de sus participantes. Y viceversa, cuanto más grande sea la comunidad y más popular sea el lenguaje de programación, más rápido podrá resolver una tarea difícil u obtener consejos de colegas.
Recolección de basuraLa recolección de basura es una forma de administración automática de memoria. Un proceso especial llamado recolector de basura periódicamente libera memoria al eliminar objetos que las aplicaciones ya no necesitan. Cada lenguaje de programación hace esto a su manera.
Python implementa el conteo de referencias a través del algoritmo stop-the-world. Pausa la ejecución del programa, inicia y ejecuta la recolección de basura, luego reanuda el proceso principal. Durante la "limpieza" aparecen 3 "generaciones" separadas: un conjunto de "pilas de basura". Zero contiene los objetos "más frescos", luego siguen las generaciones 1 y 2.
import gc import ctypes gc.set_debug(gc.DEBUG_SAVEALL) class PyObject(ctypes.Structure): _fields_ = [("refcnt", ctypes.c_long)] object1 = {} object2 = {} object3 = {} object1['reference_to_2'] = object2 object2['reference_to_1'] = object1 object3['some_key'] = 1 object1_memory_address = id(object1) object2_memory_address = id(object2) object3_memory_address = id(object3) print "Before garbage collection --->" print "Refcount for object1: {count}".format( count=PyObject.from_address(object1_memory_address).refcnt ) print "Refcount for object2: {count}".format( count=PyObject.from_address(object2_memory_address).refcnt ) print "Refcount for object3: {count}".format( count=PyObject.from_address(object3_memory_address).refcnt ) del object1, object2, object3 gc.collect() print "After garbage collection --->" print "Refcount for object1: {count}".format( count=PyObject.from_address(object1_memory_address).refcnt ) print "Refcount for object2: {count}".format( count=PyObject.from_address(object2_memory_address).refcnt ) print "Refcount for object3: {count}".format( count=PyObject.from_address(object3_memory_address).refcnt ) print "Objects that cannot be cleaned up by reference counting: --->" for x in gc.garbage: print x
Implementación del recolector de basura Python
Los resultados del código del programa anteriorPHP (comenzando con la versión PHP5.3) usa otra opción de recolección de basura junto con el conteo de referencias. Aquí, este proceso, si es necesario, se realiza junto con el programa. Se eliminan los subgráficos que no se pueden alcanzar desde la raíz.
Swift también utiliza el recuento de referencias; no hay otras formas de recolectar basura. El caso se muestra a continuación cuando el contador "fuerte" del objeto llega por completo a 0 y borra Persona (ya que está débilmente correlacionado con Apartamento).
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { print("\(name) is being deinitialized") } } class Apartment { let unit: String init(unit: String) { self.unit = unit } weak var tenant: Person? deinit { print("Apartment \(unit) is being deinitialized") }

Hay muchos ejemplos de mecanismos de recolección de basura implementados en otros idiomas. Pueden afectar el rendimiento general de la aplicación o el trabajo sin afectar la ejecución de la tarea principal.
Conceptos duplicados
Crear y administrar paquetesConozca los mecanismos para almacenar y rastrear dependencias, así como las formas de mantener información sobre el "ensamblaje" (descripción del paquete, cómo ejecutar pruebas unitarias, configurar y preparar el entorno, etc.).
Python usa
pip en conjunto con un archivo require.txt para administrar las dependencias y
setup.py para administrar la configuración del entorno , Haskell trabaja con
Cabal para resolver ambos problemas, Java tiene
Maven y
Gradle , en el caso de Scala
SBT funciona, PHP usa
Composer , NodeJS -
npm , etc.
Asegúrese de decidir la localización del entorno de desarrollo; es posible que desee ejecutar diferentes versiones de lenguajes de programación según el proyecto. Phpbrew para PHP, pyenv para Python y nvm para NodeJS lo hacen posible.
Usando pyenv, puedes trabajar con diferentes versiones de Python.En casos especiales, sucede que la biblioteca utilizada en un proyecto se instala automáticamente en otros. Esto es cierto, en particular, para lenguajes como Python y Haskell. Para evitar este problema, debe usar
virtualenv / venv para Python,
virtphp para PHP y
Cabal Sandboxes para Haskell.
Entrada / salida asincrónicaEsta es una oportunidad para aumentar el rendimiento de E / S de los datos de la aplicación. Al mismo tiempo, cada hilo funciona con su propio conjunto de registros e información de pila.

const https = require("https"); const urlList = [ "https://reqres.in/api/users?page=1", "https://reqres.in/api/users?page=2", "https://reqres.in/api/users?page=3" ]; function getSiteContents(url) { return new Promise(function (resolve, reject) { https.get(url, function (res) { var bodyData = ""; res.on("data", function (chunk) { bodyData += chunk; }); res.on("end", function () { resolve(bodyData); }); res.on("error", function (error) { reject(error.message); }); }); }); }
Implementación de E / S asíncronas usando JavascriptProgramación funcionalLa programación funcional le permite "decirle" a la computadora a un alto nivel lo que realmente quiere de ella. La mayoría de los idiomas actuales tienen las capacidades más básicas para implementar esto: a través del
mapa ,
filtro ,
reducción de listas , etc. Pero todavía vale la pena usarlo. A continuación se muestra un ejemplo de programación funcional en un lenguaje que no parece implicar tal posibilidad.
<?php
Entrenamiento
La primera etapa es la búsqueda de la información necesaria sobre recursos especializados y la creación de un pequeño proyecto después de completar la capacitación básica. En la mayoría de los casos, puede usar artículos como "Aprenda X en días Y", muchos de los cuales son muy buenos. En muchos casos, hay ejemplos de capacitación interactivos:
un recorrido por GoLang y
GoLang por ejemplo (para GoLang),
ejercicios de línea de comandos de NodeSchool (para Javascript, a saber, NodeJS),
ejercicios de Scala (para Scala),
Python Koans (para Python), etc. .p.
Comenzar con algo complicado no vale la pena. Crear pequeñas aplicaciones y scripts es lo que necesita un principiante. El número total de líneas de código en tales experimentos no supera los 300-400. Lo principal que es necesario en esta etapa es obtener información básica, aprender a programar a cualquier velocidad normal y lo más importante es comprender lo que está haciendo.
func containedClosureIncrementer() -> (() -> Int) { var anInt = 0 func incrementer() -> Int { anInt = anInt + 1 return anInt } return incrementer } func containedClosureIncrementer2() -> () -> Int { var anInt = 0 return { anInt = anInt + 1 return anInt } } let closureIncrementer = containedClosureIncrementer() print("containedClosureIncrementer call - should be 1: \(closureIncrementer() == 1)") print("containedClosureIncrementer call - should be 2: \(closureIncrementer() == 2)") var someOptionalValue: Optional<String> = nil; print("Optional - someOptionalValue is null: \(someOptionalValue == nil)") someOptionalValue = "real value" print("Optional - someOptionalValue is 'real value' \(someOptionalValue == "real value")") (["real value", nil] as Array<Optional<String>>).forEach({ someOptionalValue in if let someValue = someOptionalValue { if someValue.hasPrefix("real") { print("someValue: has real") } else { print("someValue: doesn't have real") } } else { print("someValue: has nil") } }) if (someOptionalValue ?? "").hasPrefix("real") { print("Has real 2") } else { print("Doesn't have real") } let numbersList: [Int] = Array(1...10) print("List of numbers 1 to 10: \(numbersList)") let numbersListTimes2 = numbersList.map({ (someNumber: Int) -> Int in let multiplicand = 2 return someNumber * multiplicand }) let numbersListTimes2V2 = numbersList.map({ number in number * 2 }) let numbersListTimes2V3 = numbersList.map { $0 * 2 } print("List of numbers * 2: \(numbersListTimes2)") print("V1, V2 Map operations do the same thing: \(numbersListTimes2 == numbersListTimes2V2)") print("V1, V3 Map operations do the same thing: \(numbersListTimes2 == numbersListTimes2V3)") func testGuard() { let someOptionalValue: Optional<String> = nil; guard let someOptionalValueUnwrapped = someOptionalValue else { print("testGuard: Thrown exception - nil value") return } print("testGuard: no exception - non-nil value: \(someOptionalValueUnwrapped)") } testGuard() class RuntimeError: Error {} [{throw RuntimeError()}, {1} as () throws -> Int].forEach { let returnValue = try? $0() if let returnValueUnwrapped = returnValue { print("List of closures: A normal value was returned \(returnValueUnwrapped)") } else { print("List of closures: An error was thrown") } }
Un ejemplo de un script inicial que le da una idea a un programador novato sobre cómo funciona su códigoLa segunda etapa es un estudio más profundo del lenguaje, la creación de un proyecto completo, que ya no se puede llamar "infantil". En muchos casos, debe familiarizarse con la documentación oficial. Para Javascript, se trata de
Mozilla Developer Docs , para Swift,
Swift Official Docs , para Java,
Java Learning Trails , para Python,
Python Official Docs t. Se debe prestar especial atención a los cursos en línea con buenos profesores.
También vale la pena explorar otros proyectos de código abierto. Los recursos como la
fuente jQuery anotada o la
fuente BackboneJS anotada dan una idea de cómo se utiliza un lenguaje de programación particular y bibliotecas adicionales en proyectos profesionales.
Todo esto ayudará a crear su propio proyecto serio, por ejemplo, una aplicación de escritorio, una aplicación web, un programa móvil. Intente utilizar bibliotecas externas cuando necesite herramientas y funciones adicionales.

No se olvide del rendimiento de su aplicación, siempre trate de obtener información de las últimas fuentes. Las oportunidades de aprendizaje son infinitas, puedes mejorar para siempre. Pero al final, puede sentir que se ha convertido en un profesional desde un principiante, y simplemente no hay mejor sensación en el mundo.
Skillbox recomienda: