Julia incoraggia i programmatori a mettere quanto più codice possibile dentro funzioni che possono essere compilate JIT, e creare tante funzioni piccole è, per scelta progettuale, performante.
Questo tende a lasciare molte funzioni piccole e semplici, che vanno combinate per svolgere compiti non banali.
Un approccio ovvio è annidare le chiamate di funzione. L'esempio seguente è molto artificioso, ma rende l'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)
Lo svantaggio di questo approccio è che la leggibilità cala rapidamente man mano che l'annidamento diventa più profondo.
Ci serve un approccio più semplice e più flessibile.
È la tecnica prediletta dai matematici, e Julia ne copia la sintassi matematica.
Un numero arbitrario di funzioni può essere composed insieme con gli operatori ∘ (che si digitano come \circ e poi tab).
Il risultato può essere usato come un'unica funzione.
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 paio di punti da tenere presente:
Un'alternativa può essere vista come l'approccio dei programmatori, piuttosto che dei matematici.
Pipelines sono usate da tempo negli script di shell Unix, e più di recente sono diventate popolari nei linguaggi di programmazione mainstream (a F# viene talvolta attribuito il merito di averne pionieristicamente diffuso l'adozione).
Il concetto di base è partire da alcuni dati, poi passarli attraverso una sequenza di funzioni per ottenere il risultato.
L'operatore pipe è |> (come in F# e nelle versioni recenti di R), anche se Julia ha anche una versione 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)
Ora l'esecuzione è strettamente da sinistra verso destra: l'output di ogni funzione scorre nella direzione della freccia e diventa l'input per la funzione successiva.
Non è un caso che le funzioni usate per illustrare la composizione e la pipeline prendano tutte un singolo argomento.
Alcuni linguaggi puramente funzionali passano il primo argomento dentro una funzione, ma consentono di includerne altri.
Al contrario, Julia si aspetta in una pipeline solo i nomi delle funzioni (o qualcosa di equivalente), senza argomenti aggiuntivi.
Ci sono importanti ragioni tecniche per questo (legate al fatto che currying non fa parte in modo standard del design del linguaggio).
Le molte persone che non hanno idea di cosa sia il currying devono solo accettare che questo limite non è una svista dovuta a negligenza e non è destinato a cambiare nelle versioni future di Julia.
Ci servono funzioni a singolo argomento che facciano tutto ciò che serve. Per fortuna, definire nuove funzioni in Julia è facile.
Nel modo più semplice, potremmo usare una anonymous function.
Per esempio, se abbiamo una singola stringa di input e vogliamo dividerla sugli underscore:
julia> "my_test_strings" |> (s -> split(s, '_'))
3-element Vector{SubString{String}}:
"my"
"test"
"strings"
Quel vettore potrebbe poi essere passato in pipeline ad altre funzioni, come prima.
Racchiudere la funzione anonima tra parentesi è facoltativo in questo caso, ma in generale è un modo utile per ridurre le ambiguità.
Allo stesso modo, potremmo creare una funzione con un nome, prima nel programma, e riutilizzarla quando serve.
In questo esercizio, aiuterai due innamorati del liceo a dichiarare il loro amore sui social media, generando un cuore unicode con le loro iniziali:
❤ J. + M. ❤
Implementa il metodo cleanupname.
Deve prendere un nome e rimuovere tutti i caratteri -, sostituendoli con uno spazio.
Deve anche rimuovere gli spazi bianchi all'inizio e alla fine del nome.
julia> cleanupname("Jane-Ann")
"Jane Ann"
Implementa il metodo firstletter.
Deve prendere un nome e restituire la sua prima lettera come stringa.
Assicurati di riutilizzare cleanupname, che hai definito nel passo precedente, e di comporla con altre funzioni.
julia> firstletter("Jane")
"J"
Implementa il metodo initial.
Deve prendere un nome e restituire la sua prima lettera, in maiuscolo, seguita da un punto.
Assicurati di riutilizzare firstletter, che hai definito nel passo precedente.
initial("Robert")
"R."
Implementa il metodo couple.
Deve prendere due nomi e restituire le iniziali con intorno i cuori emoji.
Assicurati di riutilizzare initial, che hai definito nel passo precedente.
couple("Blake Miller", "Riley Lewis")
"❤ B. + R. ❤"
Iscriviti a Exercism per imparare e padroneggiare Julia con 35 concetti128 esercizi e il mentoring di persone reali, tutto gratis.