A Julia incentiva os programadores a colocar o máximo de código possível dentro de funções que possam ser compiladas JIT, e criar muitas funções pequenas é, por conceção, eficiente.
Isso tende a deixar muitas funções pequenas e simples, que é preciso combinar para realizar tarefas não triviais.
Uma abordagem óbvia é aninhar chamadas de funções. O exemplo seguinte é muito forçado, mas ilustra a ideia.
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)
A desvantagem desta abordagem é que a legibilidade cai rapidamente à medida que o aninhamento se aprofunda.
Precisamos de uma abordagem mais simples e mais flexível.
Esta é a técnica preferida dos matemáticos, e a Julia copia a sintaxe matemática.
Um número arbitrário de funções pode ser composed com operadores ∘ (introduzido como \circ seguido de tab).
O resultado pode ser usado como uma única função.
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"])
Alguns pontos a ter em conta:
Uma alternativa pode ser encarada como a abordagem dos programadores, em vez da dos matemáticos.
Os Pipelines são usados há muito tempo em scripts de shell do Unix e, mais recentemente, tornaram-se populares em linguagens de programação mais convencionais (por vezes, atribui-se ao F# o pioneirismo na sua adoção).
O conceito básico é começar com alguns dados e depois encadeá-los através de uma sequência de funções para obter o resultado.
O operador de pipe é |> (como no F# e em versões recentes do R), embora a Julia também tenha uma versão broadcast, .|>.
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)
A execução passa agora a ser estritamente da esquerda para a direita, com o resultado de cada função a fluir no sentido da seta para se tornar no valor de entrada da função seguinte.
Não é por acaso que as funções usadas para ilustrar a composição e os pipelines recebem todas um único argumento.
Algumas linguagens puramente funcionais encaminham o primeiro argumento para uma função, mas permitem incluir outros.
Em contrapartida, a Julia só espera nomes de funções (ou algo equivalente) num pipeline, sem quaisquer argumentos adicionais.
Há razões técnicas importantes para isto (relacionadas com o facto de o currying não ser uma parte padrão do desenho da linguagem).
As muitas pessoas que não compreendem o currying devem apenas aceitar que esta limitação não é um descuido, nem é provável que mude em versões futuras da Julia.
Precisamos de funções com um único argumento que façam o que for necessário. Felizmente, definir novas funções na Julia é fácil.
O mais simples é usar uma anonymous function.
Por exemplo, se tivermos uma única string de entrada e quisermos dividir pelos underscores:
julia> "my_test_strings" |> (s -> split(s, '_'))
3-element Vector{SubString{String}}:
"my"
"test"
"strings"
Esse vetor poderia depois ser encaminhado para outras funções, como antes.
Neste caso, envolver a função anónima entre parênteses é opcional, mas, de um modo geral, é uma forma útil de reduzir ambiguidades.
Da mesma forma, poderíamos criar uma função com nome, mais cedo no programa, e reutilizá-la conforme necessário.
Neste exercício, vais ajudar namorados do secundário a declarar o seu amor nas redes sociais, gerando um coração unicode com as iniciais deles:
❤ J. + M. ❤
Implementa o método cleanupname.
Deve receber um nome, remover todos os carateres - e substituí-los por um espaço.
Deve também remover os espaços em branco no início e no fim do nome.
julia> cleanupname("Jane-Ann")
"Jane Ann"
Implementa o método firstletter.
Deve receber um nome e devolver a primeira letra desse nome como string.
Certifica-te de que reutilizas o cleanupname que definiste no passo anterior, combinando-o com outras funções.
julia> firstletter("Jane")
"J"
Implementa o método initial.
Deve receber um nome e devolver a sua primeira letra, em maiúscula, seguida de um ponto.
Certifica-te de que reutilizas o firstletter que definiste no passo anterior.
initial("Robert")
"R."
Implementa o método couple.
Deve receber dois nomes e devolver as iniciais com corações emoji à volta.
Certifica-te de que reutilizas o initial que definiste no passo anterior.
couple("Blake Miller", "Riley Lewis")
"❤ B. + R. ❤"
Inscreve-te no Exercism para aprenderes e dominares Julia com 35 conceitos128 exercícios, e mentoria humana real, tudo grátis.