Julia anima a quienes programan a poner la mayor cantidad de código posible dentro de funciones que se puedan compilar JIT, y crear muchas funciones pequeñas es, por diseño, eficiente.
Eso tiende a dejar muchas funciones pequeñas y simples, que hay que combinar para llevar a cabo tareas no triviales.
Una opción obvia es anidar llamadas a funciones. El siguiente ejemplo es muy rebuscado, pero ilustra el punto.
julia> first.(titlecase.(reverse.(["my", "test", "strings"])))
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
La desventaja de este enfoque es que la legibilidad cae rápidamente a medida que el anidamiento se hace más profundo.
Necesitamos un enfoque más simple y más flexible.
Esta es la técnica que adoran los matemáticos, y Julia copia la sintaxis matemática.
Un número arbitrario de funciones se pueden composed entre sí con operadores ∘ (se escribe \circ y luego tab).
El resultado se puede usar como una sola función.
julia> compfunc = first ∘ titlecase ∘ reverse
first ∘ titlecase ∘ reverse
julia> compfunc.(["my", "test", "strings"])
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
# alternative syntax, giving the same result
julia> (first ∘ titlecase ∘ reverse).(["my", "test", "strings"])
Un par de cosas que conviene notar:
Una alternativa podría considerarse el enfoque de los programadores, en lugar del de los matemáticos.
Pipelines se han usado durante mucho tiempo en los scripts de shell de Unix y, más recientemente, se volvieron populares en los lenguajes de programación más usados (a veces se atribuye a F# haber sido pionero en su adopción).
El concepto básico es empezar con algunos datos y luego hacerlos pasar por una secuencia de funciones para obtener el resultado.
El operador de tubería es |> (como en F# y las versiones recientes de R), aunque Julia también tiene una versión con broadcasting, .|>.
julia> ["my", "test", "strings"] .|> reverse .|> titlecase .|> first
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
Ahora la ejecución es estrictamente de izquierda a derecha, y la salida de cada función fluye en la dirección de la flecha para convertirse en el argumento de la siguiente función.
No es casualidad que las funciones usadas para ilustrar la composición y la canalización tomen todas un único argumento.
Algunos lenguajes puramente funcionales canalizan el primer argumento hacia una función, pero permiten que se incluyan otros.
En cambio, Julia solo espera nombres de funciones (o algo equivalente) en una canalización, sin argumentos adicionales.
Hay razones técnicas importantes para esto (relacionadas con el hecho de que currying no forma parte estándar del diseño del lenguaje).
Las muchas personas que no entienden el currying solo deberían aceptar que esta limitación no es un descuido, y que no es probable que cambie en futuras versiones de Julia.
Necesitamos funciones de un solo argumento que hagan lo que haga falta. Por suerte, definir funciones nuevas en Julia es fácil.
Lo más simple sería usar una anonymous function.
Por ejemplo, si tenemos un único string de entrada y queremos dividirlo por guiones bajos:
julia> "my_test_strings" |> (s -> split(s, '_'))
3-element Vector{SubString{String}}:
"my"
"test"
"strings"
Ese vector podría luego canalizarse hacia otras funciones, como antes.
Encerrar la función anónima entre paréntesis es opcional en este caso, pero en general es una buena forma de reducir la ambigüedad.
Del mismo modo, podríamos crear una función con nombre, antes en el programa, y reutilizarla cuando haga falta.
En este ejercicio, vas a ayudar a unos novios de secundaria a declarar su amor en las redes sociales generando un corazón unicode con sus iniciales:
❤ J. + M. ❤
Implementa el método cleanupname.
Debe recibir un nombre, quitar todos los caracteres - y reemplazarlos por un espacio.
También debe quitar los espacios en blanco del principio y del final del nombre.
julia> cleanupname("Jane-Ann")
"Jane Ann"
Implementa el método firstletter.
Debe recibir un nombre y devolver su primera letra como string.
Asegúrate de reutilizar cleanupname, que definiste en el paso anterior, y de combinarla con otras funciones.
julia> firstletter("Jane")
"J"
Implementa el método initial.
Debe recibir un nombre y devolver su primera letra, en mayúscula, seguida de un punto.
Asegúrate de reutilizar firstletter, que definiste en el paso anterior.
initial("Robert")
"R."
Implementa el método couple.
Debe recibir dos nombres y devolver las iniciales rodeadas de corazones emoji.
Asegúrate de reutilizar initial, que definiste en el paso anterior.
couple("Blake Miller", "Riley Lewis")
"❤ B. + R. ❤"
Regístrate en Exercism para aprender y dominar Julia con 35 conceptos128 ejercicios y mentoría humana real, todo gratis.