Track
/
Julia
Julia
/
Esercizi
/
L'amore del liceo
L'amore del liceo

L'amore del liceo

Esercizio di apprendimento

Introduzione

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.

Composizione

È 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:

  • Le funzioni di partenza compaiono nello stesso ordine che hanno quando annidi, e vengono eseguite da destra verso sinistra.
  • Il broadcasting non è semplice da usare quando componi, ma può essere applicato quando chiami la funzione composta.

Pipeline

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.

Limiti, soluzioni alternative e altre opzioni

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.

Soluzioni alternative

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.

Istruzioni

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. ❤

1. Pulisci il nome

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"

2. Ottieni la prima lettera del nome

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"

3. Formatta la prima lettera come iniziale

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."

4. Metti le iniziali dentro il cuore

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. ❤"
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
Julia Exercism

Vuoi iniziare L'amore del liceo?

Iscriviti a Exercism per imparare e padroneggiare Julia con 35 concetti128 esercizi e il mentoring di persone reali, tutto gratis.