Adrianistán

El blog de Adrián Arroyo


Lenguajes de programación que todo buen programador debe conocer

- Adrián Arroyo Calle

Dice Bjarne Stroustrup (creador de C++) que nadie debería llamarse un profesional si no conoce al menos 5 lenguajes suficientemente diferentes entre sí. Comparto con él esa afirmación, así que he decidido hacer una lista con esos 5 lenguajes suficientemente diferentes entre sí. La razón de que sean diferentes entre sí es que implementan paradigmas distintos.

Paradigmas de programación


Cada lenguaje está moldeado en base a uno o varios paradigmas de programación. Aunque no hay una teoría con la que todos los autores esten de acuerdo, bajo mi punto de vista existen dos grandes grupos de paradigmas de programación. Imperativos y Declarativos. Los imperativos responden a la pregunta de ¿Cómo se calcula esto? y los declarativos ¿Cuál es el resultado de esto?. Otra manera de verlo es ver al paradigma imperativo como un intento de simplificar la electrónica subyacente. El paradigma declarativo por contra muchas veces se origina de la teoría matemática y posteriormente se aplica al ordenador.

Cada uno de estos paradigmas a su vez tienen más sub-paradigmas y luego existen paradigmas transversales, es decir, que se pueden aplicar (o no) tanto en lenguajes imperativos como en lenguajes declarativos.

Un buen programador necesita conocer estos paradigmas.

Prolog


Prolog es un claro ejemplo de programación lógica. Se trata de un lenguaje declarativo. Diseñado en los años 70 en Francia, Prolog tuvo mucha popularidad en el desarrollo de Inteligencia Artificial debido a sus características lógicas. En esencia, Prolog se basa en la demostración de predicados, similares a los del álgebra de predicados.

Ejemplos de lógica de predicados

Un programa Prolog es en realidad un conjunto de afirmaciones o predicados. En tiempo de ejecución se realizan preguntas sobre predicados. Prolog intenta entonces demostrar la veracidad del predicado, para ello usa el mecanismo de backtracing. Una característica muy interesante de Prolog es el pattern matching, que básicamente permite preguntar para qué valor de una variable se cumple un predicado. Esto permite realizar cosas muy interesantes:
% PROGRAMA EN PROLOG
% PREDICADOS QUE SON VERDADEROS

hijo(bernardo,sonia).
hijo(veronica,sonia).
hijo(bernardo,luis).
hijo(veronica,luis).

varon(bernardo).
varon(luis).
mujer(veronica).
mujer(sonia).

% REGLAS

madre(X,Y) :- hijo(X,Y), mujer(X).

Ahora, para saber quién es la madre de Sonia intentamos demostrar:
?- madre(X,sonia).

Y responderá X = veronica.

En predicados sin variables, Prolog solo devuelve true o false.

Existen varios compiladores/intérpretes de Prolog, siendo el más conocido SWI-Prolog, multiplataforma y con una extensa librería que incluye GUI multiplataforma y framework web. También existe GNU Prolog y Visual Prolog (antiguamente conocido como Turbo Prolog), aunque este último no se le considera Prolog auténtico por ser demasiado diferente al resto.

Haskell


Haskell es un lenguaje declarativo que implementa el paradigma funcional. Es uno de los pocos lenguajes funcionales que son 100% puros. Se entiende por puros como la capacidad de no generar efectos colaterales. Haskell es un lenguaje fuertemente tipado y deriva de la teoría de categorías. Haskell ha sido objeto (hasta cierto punto merecido) de muchas bromas sobre este asunto, ya que para la mayoría de programadores, conocer teoría de categorías no es demasiado práctico.

Otra característica de Haskell es que es perezoso, lo que significa que no calculará nada que no sea estrictamente necesario (esto puede parecer muy raro hasta que lo entiendes en la práctica).
module Main where

import Data.Matrix
import qualified Data.Vector as Vector
import qualified Reader
import qualified Data.Maybe as Maybe

main :: IO ()
main = do
matrix <- Reader.readFile "gosperglidergun.txt"
putStrLn "Iterar cuantas veces?"
n <- getLine
let times = read n :: Int
let finalMatrix = Prelude.iterate Main.iterate matrix
putStrLn $ prettyMatrix $ finalMatrix !! times


iterate :: Matrix Int -> Matrix Int
iterate m =
if hasToGrow then
matrix (nrows m +2) (ncols m +2) (\(i,j) -> builder (i-1,j-1))
else
matrix (nrows m) (ncols m) builder
where
builder (i,j) =
if get (i,j) == 0 then
if hasToBorn (i,j) then
1
else
0
else
if hasToDie (i,j) then
0
else
1
hasToGrow =
Vector.sum (getCol (ncols m) m) > 0 ||
Vector.sum (getRow (nrows m) m) > 0 ||
Vector.sum (getCol 1 m) > 0 ||
Vector.sum (getRow 1 m) > 0
get (i,j) = Maybe.fromMaybe 0 (safeGet i j m)
hasToBorn (i,j) = sumNeighbors (i,j) == 3
hasToDie (i,j) = sumNeighbors (i,j) /= 2 && sumNeighbors (i,j) /= 3
sumNeighbors (i,j) =
get (i-1,j-1) + get (i,j-1) + get (i+1,j-1)
+ get (i-1,j) + get (i+1,j)
+ get (i-1,j+1) + get (i,j+1) + get (i+1,j+1)

Este código pertenece al juego de la vida de Conway que (también) implementé en Haskell, simplemente por curiosidad, ya que no está optimizado. Faltaría el módulo Reader, así que no intentéis compilarlo directamente.

Haskell como tal no tiene variables ni bucles y sus condicionales no son exactamente iguales a los de los lenguajes imperativos (aunque se use if, en el caso de Haskell son bloques que siempre deben devolver un valor).

Aunque siempre ha sido un lenguaje académico (nació en 1994, un año antes que Java), ahora ha alcanzado bastante popularidad y algunas empresas como Facebook lo usan en producción.

El compilador más conocido, capaz de generar código nativo, es GHC. Este dispone de un REPL llamado GHCi y también compila a JavaScript y se está trabajando en WebAssembly. Otro intérprete es Hugs, pero solo es compatible con Haskell98.

La forma recomendada de instalar GHC es con Stack. Usa Stack y ahórrate quebraderos de cabeza.

Otros lenguajes similares a Haskell son ElmPureScriptEta e Idris. Este último compila a JavaScript y pone énfasis en su librería, que es capaz de competir con Angular y React.

Racket (Lisp)


Lisp no puede faltar nunca. Se trata de uno de los primeros lenguajes de programación y sigue siendo tan actual como el primer día, gracias a su simple pero efectivo diseño, inspirado en el cálculo lambda de Alonzo Church. No obstante, Lisp ha evolucionado mucho, si me preguntan que dialecto/lenguaje de Lisp merece la pena aprender ahora diría Racket. Racket es un lenguaje de programación funcional y programación orientada a objetos. Racket no es 100% puro como Haskell (la mayoría de dialectos de Lisp no lo son) pero sigue siendo muy interesante. También, tiene tipado débil en contraposición al tipado fuerte de Haskell.

Racket desciende a su vez de Scheme, que es una de los ramas principales de Lisp, siendo la otra Common Lisp. ¿Por qué estas diferencias? La gente de Scheme prefiere un lenguaje elegante, lo más funcional posible mientras que la gente de Common Lisp prefirieron sacrificar eso a cambio de un lenguaje más práctico en el mundo real.

Racket cuenta con una extensísima librería estándar capaz de realizar todo lo que te imagines sin gran problema. Racket también soporta las famosas y potentes macros.
#lang racket

(require 2htdp/image) ; draw a picture
(let sierpinski ([n 8])
(cond
[(zero? n) (triangle 2 'solid 'red)]
[else (define t (sierpinski (- n 1)))
(freeze (above t (beside t t)))]))

Triángulo de Sierpinski en el IDE DrRacket.

JavaScript


JavaScript está en todas partes. Es uno de los lenguajes con mayor aplicación práctica. Web, servidor, bases de datos, scripting, plugins e incluso IoT. Por eso JavaScript me parece un lenguaje que deba estar en esta lista. Y sin embargo no es fácil categorizarlo correctamente. Ante todo, estamos ante un lenguaje de programación imperativo, con orientación a objetos y buen soporte a la orientación a eventos.

Y antes de que se me venga alguien a comerme, sí, JavaScript está orientado a objetos, aunque no siguen el patrón de clase/herencia que C++ y Java tienen tan acostumbrados a la gente. La orientación a objetos por prototipos no la inventó JavaScript, sino que ya estaba en otros lenguajes como Self y más actualmente Io. Y realmente lenguajes como Python o Ruby no se alejan mucho de esto internamente.

Actualmente, con la versión ES7, tenemos muchas cosas interesantes en programación asíncrona y clases al estilo Java que no son más que azúcar sintáctico sobre el verdadero modelo de JS.

Definitivamente, JavaScript es un lenguaje muy interesante y aunque a algunas personas les pueda parecer un caos, ciertamente es muy productivo y útil. Aprovechar al máximo JavaScript requiere pensar de forma distinta a como lo harías en otros lenguajes imperativos.
async function lastGist(username){
let f = await fetch(`https://api.github.com/users/${username}/gists`);
let data = await f.json();
try{
return data[0].description;
}catch(e){
return undefined;
}
}

let users = ["aarroyoc","DanielBV","hecsaen"];
Promise.all(users.map(lastGist)).then((gists)=>{
gists.forEach((gist)=>{
console.log(`Last gist: ${gist}`);
});
});

C# (o Java)


C# es otro lenguaje bastante complejo, imperativo, orientado a objetos por clases, con partes de programación funcional y programación asíncrona. Pero el sistema de la orientación a objetos aquí es distinto. Aunque Alan Kay, creador de Smalltalk y casi casi de la orientación a objetos, opine que el estilo de C++ y de Java son putas mierdas, lo cierto es que es lo más usado actualmente. Herencia, clases, interfaces, etc

Personalmente prefiero C#, ya que como lenguaje es más potente que Java, pero ambos al final tienen bastantes características comunes entre sí.
using System;

class MainClass {
public static void Main (string[] args) {
var names = new List<String>
{
"Ana",
"Felipe",
"Emilia"
};

foreach (var name in names)
{
Console.WriteLine($"Hello {name}");
}
}
}

Para C# el compilador más usado es Roslyn, actualmente disponible en .NET Framework, .NET Core y Mono.

Otros lenguajes parecidos son Java o si preferimos una sintaxis tipo Pascal: Delphi/Object-Pascal tiene conceptos muy similares.

Conclusión


Con esto ya tendríamos 5 lenguajes suficientemente diferentes. Ahora bien, nos hemos dejado lenguajes muy interesantes, tanto desde el punto de vista práctico como teórico. No puedo dejar de mencionar a Smalltalk por su implementación de los objetos, a C por su ligera capa de abstracción sobre ensamblador, al propio Ensamblador porque es lo que ejecuta la CPU realmente, a Python por su diseño así como su gran uso en ciencia de datos y scripts o a Rust, un lenguaje imperativo con sistema de traits, semántica de movimiento, pattern matching.

Merece también la pena mirar FORTH, SQL (quizá el lenguaje declarativo más usado del mundo) y lenguajes que se adapten bien a la programación reactiva (hay sobre todo librerías, aunque algún lenguaje como Simulink lo implementa). Mathemathica implementa también el paradigma de programación simbólica, muy poco explotado en otros lenguajes.

Estos han sido mis 5 lenguajes, ¿cuáles serían los tuyos?

Comentarios

LuigiMX
Yo agregaría F#. Es cierto que no es tan famoso o usado como c#, pero al ser un lenguaje multiparadigma, puede hacer casi cualquier cosa que hace c#, mas todo lo que puede hacer el mismo f# que no hace c#. Aparte, al tener soporte de .net (y .net core), tiene a su disposición una cantidad bastante interesante de librerías.

Añadir comentario

Todos los comentarios están sujetos a moderación