domingo, 27 de agosto de 2017

Empezando con Elixir 6


Funciones

En Elixir y muchos lenguajes funcionales, las funciones son ciudadanos de primera clase.

Funciones Anónimas

Tal como su nombre lo indica, una función anónima no tiene nombre. Como vimos en la lección de Enum, éstos se pasan con frecuencia a otras funciones. Para definir una función anónima en Elixir necesitamos las palabras clave fn y end. Dentro de éstos podemos definir cualquier número de parámetros y cuerpos funcionales separados por ->

iex> sum = fn (a, b) -> a + b end
iex> sum.(2, 3)
5

El operador &

El uso de funciones anónimas es una práctica común en Elixir hay una forma abreviada de hacerlo:

iex> sum = &(&1 + &2)
iex> sum.(2, 3)
5

Como probablemente ya se supone, en la versión abreviada nuestros parámetros están disponibles para nosotros como & 1, & 2, & 3, y así sucesivamente.

Pattern Matching

Pattern matching no esta limitado solo a variables sino que tambien se pueden utilizar en funciones.

Se pueden utilizar pattern matching con el primer conjunto de parámetros que coinciden e invoca el cuerpo correspondiente:

iex> handle_result = fn
...>   {:ok, result} -> IO.puts "Handling result..."
...>   {:error} -> IO.puts "An error has occurred!"
...> end

iex> some_result = 1
iex> handle_result.({:ok, some_result})
Handling result...

iex> handle_result.({:error})

An error has occurred!

Funciones con nombre

Podemos definir funciones con nombre utilizando la palabra clave def. Las funciones definidas dentro de un modulo pueden ser accedidas por otros módulos:

defmodule Greeter do
  def hello(name) do
    "Hello, " <> name
  end
end

iex> Greeter.hello("Sean")
"Hello, Sean"

Si queremos definir una función en una linea podemos usar do:

defmodule Greeter do
  def hello(name), do: "Hello, " <> name
end

Y podemos utilizar pattern matching:

defmodule Length do
  def of([]), do: 0
  def of([_ | tail]), do: 1 + of(tail)
end

iex> Length.of []
0
iex> Length.of [1, 2, 3]
3

Funciones con nombre y número de argumentos

Como el lenguaje es de tipado débil, la firma de la función esta dada por el nombre de la función y la cantidad de parámetros. Es decir las funciones aceptan sobre carga y esta dada por el numero de parametros:

defmodule Greeter2 do
  def hello(), do: "Hello, anonymous person!"   # hello/0
  def hello(name), do: "Hello, " <> name        # hello/1
  def hello(name1, name2), do: "Hello, #{name1} and #{name2}"
                                                # hello/2

end

A diferencia de otros lenguajes, la firma de una función esta dada por el nombre y el numero de parámetros.

Funciones privadas

Una función privada solo se puede llamar dentro del modulo y se define con  defp :

defmodule Greeter do
  def hello(name), do: phrase <> name
  defp phrase, do: "Hello, "
end

iex> Greeter.hello("Sean")
"Hello, Sean"

iex> Greeter.phrase
** (UndefinedFunctionError) undefined function: Greeter.phrase/0
    Greeter.phrase()

Guards

En el ejemplo siguiente tenemos dos funciones con la misma firma, nos basamos en guards para determinar qué utilizar en función del tipo del argumento:

defmodule Greeter do
  def hello(names) when is_list(names) do
    names
    |> Enum.join(", ")
    |> hello
  end

  def hello(name) when is_binary(name) do
    phrase() <> name
  end

  defp phrase, do: "Hello, "
end

iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"

Argumentos por defecto

Se pueden utilizar argumentos por defecto:

defmodule Greeter do
  def hello(name, language_code \\ "en") do
    phrase(language_code) <> name
  end

  defp phrase("en"), do: "Hello, "
  defp phrase("es"), do: "Hola, "
end

iex> Greeter.hello("Sean", "en")
"Hello, Sean"

iex> Greeter.hello("Sean")
"Hello, Sean"

iex> Greeter.hello("Sean", "es")
"Hola, Sean"

Cuando combinamos nuestro ejemplo de guard con argumentos predeterminados, nos topamos con un problema. Veamos cómo podría verse:

defmodule Greeter do
  def hello(names, language_code \\ "en") when is_list(names) do
    names
    |> Enum.join(", ")
    |> hello(language_code)
  end

  def hello(name, language_code \\ "en") when is_binary(name) do
    phrase(language_code) <> name
  end

  defp phrase("en"), do: "Hello, "
  defp phrase("es"), do: "Hola, "
end

** (CompileError) def hello/2 has default values and multiple clauses, define a function head with the defaults

Elixir no le gusta los argumentos por defecto en múltiples funciones de coincidencia, puede ser confuso. Para manejar esto agregamos una cabeza de función con nuestros argumentos por defecto:

defmodule Greeter do
  def hello(names, language_code \\ "en")
  def hello(names, language_code) when is_list(names) do
    names
    |> Enum.join(", ")
    |> hello(language_code)
  end

  def hello(name, language_code) when is_binary(name) do
    phrase(language_code) <> name
  end

  defp phrase("en"), do: "Hello, "
  defp phrase("es"), do: "Hola, "
end

iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"

iex> Greeter.hello ["Sean", "Steve"], "es"
"Hola, Sean, Steve"

Dejo link: https://elixirschool.com/en/lessons/basics/functions/