Julia incentiva quem programa a colocar o máximo de código possível dentro de funções que podem ser compiladas JIT, e criar muitas funções pequenas é, por design, performático.
Isso tende a deixar muitas funções pequenas e simples, que precisam ser combinadas para realizar tarefas não triviais.
Uma abordagem óbvia é aninhar chamadas de função. O exemplo a seguir é bem forçado, mas ilustra o ponto.
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 dessa abordagem é que a legibilidade cai rapidamente conforme o aninhamento fica mais profundo.
Precisamos de uma abordagem mais simples e mais flexível.
Essa é a técnica querida dos matemáticos, e Julia copia a sintaxe matemática.
Um número arbitrário de funções pode ser composed com operadores ∘ (digitados como \circ e depois 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 para observar:
Uma alternativa pode ser encarada como a abordagem dos programadores, em vez da dos matemáticos.
Pipelines são usados há muito tempo em scripts de shell do Unix, e mais recentemente se popularizaram nas linguagens de programação mainstream (às vezes se credita ao F# o pioneirismo na adoção deles).
O conceito básico é começar com alguns dados e então encaminhá-los por uma sequência de funções para obter o resultado.
O operador de pipe é |> (como em F# e nas versões recentes do R), embora Julia também tenha uma versão de 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 agora é estritamente da esquerda para a direita, com a saída de cada função fluindo na direção da seta para se tornar a entrada da próxima função.
Não é coincidência que as funções usadas para ilustrar composição e pipelines recebam todas um único argumento.
Algumas linguagens puramente funcionais canalizam o primeiro argumento para uma função, mas permitem que outros sejam incluídos.
Em contraste, Julia espera apenas os nomes das funções (ou algo equivalente) em um pipeline, sem nenhum argumento adicional.
Há razões técnicas importantes para isso (relacionadas ao fato de que currying não é parte padrão do design da linguagem).
As muitas pessoas que não entendem nada de currying devem apenas aceitar que essa limitação não é um descuido, e não deve mudar em versões futuras de Julia.
Precisamos de funções de um único argumento que façam o que for preciso. Felizmente, definir novas funções em Julia é fácil.
Da forma mais simples, poderíamos usar uma anonymous function.
Por exemplo, se temos uma única string de entrada e queremos dividir nos underscores:
julia> "my_test_strings" |> (s -> split(s, '_'))
3-element Vector{SubString{String}}:
"my"
"test"
"strings"
Esse vetor poderia então ser encaminhado para outras funções, como antes.
Colocar a função anônima entre parênteses é opcional neste caso, mas, de forma mais geral, é uma maneira útil de reduzir ambiguidade.
Da mesma forma, poderíamos criar uma função nomeada, antes no programa, e reutilizá-la conforme necessário.
Neste exercício, você vai ajudar namorados do ensino médio a declarar seu amor nas redes sociais, gerando um coração unicode com as iniciais deles:
❤ J. + M. ❤
Implemente o método cleanupname.
Ele deve receber um nome, remover todos os caracteres - e substituí-los por um espaço.
Ele também deve remover qualquer espaço em branco do início e do fim do nome.
julia> cleanupname("Jane-Ann")
"Jane Ann"
Implemente o método firstletter.
Ele deve receber um nome e retornar sua primeira letra como uma string.
Certifique-se de reutilizar cleanupname, que você definiu na etapa anterior, e combiná-lo com outras funções.
julia> firstletter("Jane")
"J"
Implemente o método initial.
Ele deve receber um nome e retornar sua primeira letra, em maiúscula, seguida de um ponto.
Certifique-se de reutilizar firstletter, que você definiu na etapa anterior.
initial("Robert")
"R."
Implemente o método couple.
Ele deve receber dois nomes e retornar as iniciais com corações emoji ao redor.
Certifique-se de reutilizar initial, que você definiu na etapa anterior.
couple("Blake Miller", "Riley Lewis")
"❤ B. + R. ❤"
Crie sua conta no Exercism para aprender e dominar Julia com 35 conceitos128 exercícios e mentoria humana de verdade, tudo de graça.