Ejemplos básicos con Elixir

Hello world
IO.puts "Hello world!"
Nada de clases con cosas estáticas, sencillo sencillo
También valdría…
IO.puts("Hello world!")
IO.puts es una función incluso cuando no tiene paréntesis.
Los paréntesis son opcionales, como en ruby
WARNING
No olvides no dejar espacio entre el nombre de la función y (
Ahora lo ponemos en un módulo
Hello world2
defmodule Hello_world  do

    def hello  do
        IO.puts "Hello world!"
    end

end
Y ahora un poquito más elaborado
Hello
defmodule  Basic  do

    def say_hello  name do
        IO.puts "Hello #{name}"
    end

end
Sí señor, string interpolation, esto mola
¿Cómo pueden vivir algunos sin esto (C++, Java, python…)?
Para probar, ejecutar desde iex
c"basic.ex"
Basic.say_hello  "Jose"
Y ahora un pequeño cambio
defmodule  Basic  do

    IO.puts  "I say hello you"

    def say_hello  name do
        IO.puts "Hello #{name}"
    end

end
¡¡¡sorpresa!!!.
A diferencia de Python
IO.puts  "I say hello you"
No se ejecutará al cargar el módulo la primera vez.
Se ejecutará al COMPILAR
Eso mola.
Además de las decisiones en tiempo de compilación que permitirían aumentar el rendimiento de la aplicación… se pueden hacer cosas como versionado auotmático al compilar.
Lenguaje declarativo, no tenemos bucles. Un ejemplo típico, cálculo del factorial.
Factorial non tail recursive
defmodule  Basic  do

    def factorial  0    do
        1
    end

    def factorial  num  do
        num * factorial  num-1
    end

end
No tiene recursión de cola, pero sí tiene pattern matching ¿Cómo se puede vivir sin el pattern matching?
Y… todo son expresiones. ¿Cómo se puede programar sin que todo sean expresiones?
Un poco más compactado…
defmodule  Basic  do

    def (factorial  0),    do:   1
    def (factorial  num),  do:   num * factorial  num-1

end
No está bien abusar de la recursividad sin recursión de cola
defmodule  Basic  do

    def (factorial  num),       do:   (factorial 1, num)

    defp (factorial  acc, 1),   do:   acc
    defp (factorial  acc, num), do:   (factorial  acc*num, num-1)

end
La recursión de cola permitirá al copilador hacer un jump y generar un código sin llamadas recursivas a funciones.
Esto significa que utilizará menos memoria y menos tiempo de CPU.
Pero… si cronometramos la versión recursiva y la no recursiva… no hay una diferencia significativa. ¿Cómo es posible?
Supongo que esto es una optimización de Erlang en la que es capaz de modificar una función no tail recursive a unatail recursive. Esto vi que empezó ha pasar hace un par de años (Erlang 12 o cercana)
De cualquier forma, si no sabemos en que casos se producirá la magia, lo mejor es utilizar la recursividad de cola siempre.
Pero esto no es todo lo que se puede hacer con la función factorial. Siguiendo el estilo funcional, podríamos hacerlo así.
Factorial de 100
1..100 |> Enum.reduce(1, fn(n, acc) -> n*acc end)
Y si lo queremos en una función…
defmodule  Basic  do

    def factorial(num),   do:  1..num |> Enum.reduce(1, fn(n, acc) -> n*acc end)

end
Chulo, chulo, chulo
Aquí hay miga.
1..100 es un rango ¿?
En realidad es un Stream (añadido muy recientemente en elixir)
Un Stream es un elemento iterable y perezoso (al estilo haskell)
Cartel: No dejes para mañana lo que puedas hacer hoy
"Desde mañana mismo empiezo"
Felipito— amigo de Mafalda
|> Este es una especie de operador pipe
Nos evita escribir algo como…
Enum.reduce(1..100, 1, fn(n, acc) -> n*acc end)
En este caso no sería un gran problema, pero sí es una gran mejora en muchos casos.
Hay una forma de simplificar la escritura de funciones anónimas
f = fn(x1, x2) -> x1*x2 end

f = &(&1 * &2)
Estas dos líneas son equivalentes.
Así que podemos escribir:
defmodule  Basic  do

    def factorial(num),   do:  1..num |> Enum.reduce(1, &(&1 * &2))

end
Programa 1 para buscar el primer número triangular que tiene más de 500 factores
defmodule Triangle500   do


    def find_triangle_more500f  do
        start = :erlang.now

        try_next_triangle 1, 1

        finish = :erlang.now
        IO.puts "#{(:timer.now_diff finish, start)/1_000_000}"
    end





    defp try_next_triangle  current, step   do
        nfactors =   num_factors current
        if nfactors>500 do
            IO.puts "located on step #{step}    triangle #{current}   factors #{nfactors}"
        else
            try_next_triangle  current+step+1, step+1
        end
    end


    defp num_factors  num  do
        stop = :math.sqrt num
        result = 2 * num_factors  0, num, 1, stop
        result - if num===stop*stop, do: 1, else: 0
    end

    defp num_factors  acc, num, d, stop_on  do
        if d > stop_on do
            acc
        else
            num_factors acc+ (if (rem num, d) ===0, do: 1, else: 0), num, d+1, stop_on
        end
    end

end
No está mal. Recursión de cola en la generación de números triangulares y en el cálculo de los factores. Bien.
Pero…
Si quisiéramos parametrizar el número de triángulos, con esta solución, quedaría feo.
Además… muchas líneas, mucho código. Podemos hacerlo mejor.
defmodule Triangle_factors   do


    def find_triangle_more_n_f  nfactors do
        start = :erlang.now

        [result] =
            Stream.unfold({1, 1}, fn {t, n} -> {t, {t+n+1, n+1}} end)
            |> (Stream.drop_while &(num_factors(&1) < nfactors))
            |>  Enum.take 1

        IO.puts "#{result}"
        finish = :erlang.now
        IO.puts "it took... #{(:timer.now_diff finish, start)/1_000_000} seconds"
    end

    defp num_factors  n  do
        r2 = :erlang.trunc :math.sqrt n
        (1..r2 |> Enum.count &(rem(n, &1) === 0))
            * 2 - (if n===r2*r2, do: 1, else: 0)
    end


end
Primero calculamos todos los números triangulares (sí, son infinitos, pero seguimos el consejo de Felipito)
Stream.unfold({1, 1}, fn {t, n} -> {t, {t+n+1, n+1}} end)
Esta lista de infinitos números triangulares, se la pasamos a un filtro que tira todos los que tengan menos de los factores qu ele indiquemos
|> (Stream.drop_while &(num_factors(&1) <= nfactors))
Ya tenemos la lista de infinitos números triangulares que tienen más de quinientos factores. Y esto lo calcula en unas milésismas de segundo (con el procedimiento de Felipito).
Ya sólo queda decirle… dame el primero tan sencillo como:
|>  Enum.take 1
Y como ha sido perezoso, es ahora cuando se pone a hacer los cálculos, pero no de todos, sólo hasta donde es imprescindible, porque sigue siendo perezoso.
Aunque esta implementación de num_factors es algo menos eficiente que la anterior con más código, la diferencia no es significativa.
Para terminar, una pequeña referencia sobre los paréntesis (sintáxis)
¿El uso de los paréntesis es opcional?
Yo más bien diría que Elixir tiene inferencia de paréntesis como otros lenguajes tienen inferencia de tipos.
Una de las herencias de Ruby
Cuando hay ambigüedades, hay que ponerlos. En otro caso, puedes dejar que el compilador los deduzca
Todo empezó así…
defmodule(Hello, do: (
  def(calculate(a, b, c), do: (
    =(temp, *(a, b))
    +(temp, c)
  ))
))
Con un gran olor a LISP, que está muy bien. Todo son macrosvariables o llamadas a funciones con una sintáxis muy sencilla
Pero la gente tiene una especial predilección por los operadores (o funciones infijas), así que evolucionó a…
defmodule(Hello, do: (
  def(calculate(a, b, c), do: (
    temp = a * b
    temp + c
  ))
))
Esto sigue compilando y podemos entender mucho mejor la inferencia de paréntesis
Queremos un LISP sin tantos paréntesis
Es impresionante como un lenguaje que está en desarrollo, para una máquina virtual nada popular, avanza tan rápido y con la atención de tanta gente.
La primera vez que quise poner una referencia de este lenguaje, la palabra elixir estaba vetada. Ahora, en las primeras entradas de google…
Esto es divertido y sólo es el principio

Comentarios

Entradas populares de este blog

Software libre

Servicios, servicios, servicios... (y Amazon)

Tecnologías divertidas