Julia anima a quienes programan a incluir tanto código como sea 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 sencillas, 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 la idea.
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 vuelve más profundo.
Necesitamos un enfoque más sencillo y más flexible.
Esta es la técnica predilecta de los matemáticos, y Julia copia la sintaxis matemática.
Con composed se pueden combinar entre sí un número arbitrario de funciones mediante operadores ∘ (que se escriben como \circ y luego tabulador).
El resultado se puede usar como una única 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 tener en cuenta:
Una alternativa podría considerarse el enfoque de los programadores más que el de los matemáticos.
Las Pipelines se usan desde hace mucho tiempo en los scripts de shell de Unix y, más recientemente, se han vuelto populares en los lenguajes de programación convencionales (a veces se atribuye a F# haber sido pionero en adoptarlas).
El concepto básico consiste en partir de unos datos y luego hacerlos pasar por una secuencia de funciones para obtener el resultado.
El operador de tubería es |> (como en F# y en 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)
La ejecución ahora es estrictamente de izquierda a derecha, y la salida de cada función fluye en la dirección de la flecha para convertirse en la entrada de la siguiente función.
No es casualidad que las funciones usadas para ilustrar la composición y las tuberías tomen un único argumento.
Algunos lenguajes puramente funcionales canalizan el primer argumento hacia una función, pero permiten incluir otros.
En cambio, Julia solo espera nombres de funciones (o algo equivalente) en una tubería, sin argumentos adicionales.
Hay razones técnicas importantes para ello (relacionadas con el hecho de que el currying no forma parte estándar del diseño del lenguaje).
Las muchas personas que no tienen ni idea de currying deberían limitarse a 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 sencillo 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 entonces pasarse por una tubería hacia otras funciones, como antes.
Encerrar la función anónima entre paréntesis es opcional en este caso, pero en general es una forma útil de reducir la ambigüedad.
Del mismo modo, podríamos crear una función con nombre antes en el programa y reutilizarla siempre que haga falta.
En este ejercicio, vas a ayudar a unos novios de instituto 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 y eliminar todos los caracteres -, sustituyéndolos por un espacio.
También debe eliminar 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 con corazones emoji alrededor.
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.