Funzioni e ricorsione
Le funzioni
Posso già sentire le urla di giubilo di alcuni che pensano che finalmente la lunga parte teorica di introduzione sia finita. Finalmente! ora si fa qualcosa di più tecnico ed interessante. Purtroppo per per queste persone che si aspettavano chissà quali effetti speciali sia questa che la prossima lezione saranno ancora incentrate sulla teoria, quindi mettetevi il cuore in pace. Devo ancora parlare di uno dei pilastri fondamentali della programmazioni. Le funzioni.
Fino ad ora avete sempre scritto tutto il codice dentro a dei singoli file .php. Ogni volta che questo veniva aperto il codice al suo interno veniva eseguito dall'inizio fino alla fine. L'introduzione delle strutture di controllo vi ha permesso di controllare il flusso del codice all'interno del file. Ora però è tempo di andare oltre.
Potete immaginare le funzioni come un insieme di istruzioni a cui diamo un nome. Ogni volta che chiamate questo nome all'interno del codice, il flusso si ferma e viene passato il controllo alla funzione che esegue le istruzioni al suo interno. Se ci fate caso in alcuni esempi delle lezioni precedenti ho usato spesso delle funzioni. Certo ma cosa dobbiamo fare per creare una nuova funzione? Qual'è la sintassi da usare? La risposta è veramente semplice.
Per definire una nuova funzione serve solo una cosa, il nome preceduto dalla parola chiave function. Dopo di che dovete ricordarvi di inserire una coppia di parentesi tonde e una di parentesi graffe. Le parentesi tonde racchiuderanno la lista degli eventuali argomenti da passare alla funzione. Mentre le parentesi graffe conterranno il corpo della funzione.
<?php
/* piccolo esempio che mostra una funzione veramente minima.
senza argomenti e che non ritorna alcun valore */
function nome_della_funzione()
{
$a = $b;
$c = $a;
$a = $c - $b;
}
?>
Prima di continuare un consiglio. Date sempre nomi rappresentativi alle funzioni. Come per le variabili il vostro script non andrà più veloce solo perché il nome della vostra funzione è composto da meno caratteri. Il nome serve più che altro a voi per ricordarvi cosa contiene. Facendo un esempio se dovete creare una funzione che tramite una porta seriale si colleghi alla nuova macchina del caffè espresso e prepari una nuova tazzina di espresso, è buona pratica chiamare la funzione nuova_tazzina_di_espresso() e non ntde().
Parliamo ora di quello che possiamo scrivere all'interno di una funzione. Come già detto più volte PHP vi concede una grande libertà e all'interno di una funzione potete inserire qualsiasi frammento di codice valido, dalle variabili passando per la definizione di altre funzioni fino alle classi, che vedremo più avanti.
Ora qualche esempio di funzioni più o meno semplici per chiarire eventuali dubbi a chi ne avesse.
<?php
/* esempio di una funzione molto complessa */
function esempio_1($uno, $due)
{
/* ecco una funzione dichiarata all'interno di un'altra
funzione */
function funzione_innestata()
{
echo "ciao";
}
echo "ciao "; funzione_innestata();
/* possiamo perfino dichiarare nuovi oggetti */
class oggetto
{
var $uno;
function blabla ()
{
$this->uno++;
echo $this->uno;
}
}
$obj = new Oggetto();
for ($i=0; $i<=33; $i+)
$obj->blabla ();
}
?>
I parametri di una funzione
Ora parlereremo di cosa andrà scritto tra le due parentesi tonde dopo il nome della funzione. In questo spazio viene inserita una lista finita di argomenti da passare alla funzione. Questi argomenti non sono altro che delle variabili che contengono informazioni per la funzione e il suo funzionamento. Quando definite una nuova funzione che dovrà avere dei parametri in ingresso dovrete per prima cosa ricordarvi di inserire ogni singolo parametro all'interno delle parentesi tonde. Se per esempio volete creare una funzione che calcola la media tra due valori dovrete inserire tra le parentesi tonde il nome di due variabili a piacere che poi userete all'interno della funzione per riferirvi ai due parametri.
Il solo inserimento dei due parametri nella dichiarazione della funzione non basta però. Quando andrete a chiamare la funzione dovrete infatti ricordarci di passare i vari valori che vogliate che la funzioni utilizzi. Sempre considerando l'esempio precedente della funzione che calcola la media dovrete passare i due numeri oppure due variabili contenenti i due valori di cui fare la media.
<?php
/* funzione banale */
function nome($a, $b)
{
return $a + $b;
}
/* alcune variabili che useremo come parametri */
$parametro1 = 9;
$parametro2 = 1;
/* chiamate varie */
echo nome($parametro1, $parametro2);
echo nome($parametro2, 4);
?>
Chiamate indirette
Come avete visto nel paragrafo precedente per chiamare una funzione basta scrivere il suo nome all'interno del codice e questa verrà eseguita. PHP vi permette però di usare alcune "finezze" che in altri linguaggi non è possibile usare. Avete mai avuto la necessita di poter chiamare una funzione di cui non sapete nemmeno il nome? Questo in PHP è realizzabile con poche linea di codice. Una chiamata di funzione indiretta è, per quanto il nome possa spaventare, una cosa veramente semplice. Basterà inserire all'interno di una variabile qualsiasi il nome della funzione da chiamare e usare la variabile come se fosse una vera e propria funzione aggiungendo la coppia di parentesi tonde subito dopo il nome.
<?php
/* funzione banale che crea la media */
function fai_media( $a , $b )
{
return (float)($a + $b) / 2;
}
/* ora assegnammo il nome della funzione alla variabile */
$media = "fai_media";
/* ed ecco che richiamiamo la funzione tramite la variabile */
echo "la media tra 2 e 19 è: ". $media(2, 9);
?>
Per chi volesse approfondire questo argomento volevo solo aggiungere alcune piccole informazioni. PHP vi mette a disposizione alcune funzioni da usare con questo tecnica che vi permettono di sapere a priori se la funzione che andrete a chiamare esiste o meno in modo da evitare chiamate a funzioni inesistenti. La funzione si chiama function_exists(), accetta come unico parametro il nome della funzione da controllare e ritorna vero solo se questa esiste. Nelle ultime versioni si è andato persino oltre permettendo la creazione di funzioni all'interno del codice. Grazie alla funzione create_function() è possibile creare delle funzioni mentre il codice stesso è in esecuzione.
<?php
/* 5 funzioni */
function funzione1() { echo "1"; }
function funzione2() { echo "2"; }
function funzione3() { echo "3"; }
function funzione5() { echo "4"; }
function funzione6() { echo "5"; }
for ($i=1; $i<7; $i++)
{
/* il nome della funzione da chiamare è generato durante l'esecuzione */
$funzione = "funzione$i";
/* la funzione funzione$i con $i==4 non esiste */
if (function_exists($funzione)) $funzione();
}
/* creiamo l'intera funzione dinamicamente */
$runtime = create_function('$a', 'echo "il doppio di $a è", $a*2;');
$runtime(9);
?>
Valori di ritorno di una funzione
Le funzioni possono essere divise in due grandi categorie. Quelle che una volta chiamate ritornano anche un valore che sono chiamate comunemente funzioni sia in matematica che nella maggior parte dei linguaggi di programmazione. L'altra categoria invece contiene quelle funzioni che una volta richiamate non ritornano alcun valore. Questo ultimo tipo di funzioni sono anche conosciute come metodi in alcuni linguaggi. Ora questa distinzione in PHP non esiste quindi chiameremo i due tipi funzioni.
Ma cosa dobbiamo fare per far si che una funzione ritorni un valore? La risposta a questa domanda sta nel return. Il return è già stato visto in questo corso e sappete già come funziona quindi non mi dilungherò su come utilizarlo. Ricordatevi soltanto che se usato da solo interrompe il flusso di una funzione e non ritorna alcun tipo di valore simulando un metodo quindi. Se il return senza argomenti è alla fine della funzione potete anche ometterlo. Se invece fate seguire al return una variabile o un valore questo verrà usato come valore di ritorno della funzione.
<?php
/* questa funzione è un ipotetico metodo in quanto non ritorna niente
notate come il return sia stato omesso */
function metodo()
{
echo "qua";
echo "la";
echo "su";
echo "giù";
}
/* questa è invece una funzione in quanto ritorna un valore */
function calcola_pi()
{
/* calcola il valore di pi greco ... */
return 3.14;
}
?>
Passaggio per riferimento o per valore
Parliamo ora di come il PHP passa gli argomenti alle funzioni quando queste vengono chiamate. Per default tutti i parametri vengono sempre passati per valore ciò vuol dire che al momento della chiamata viene fatta una copia del valore di ogni variabile e solo questa copia viene usata dalla funzione. Cosi facendo se all'interno della funzione il valore viene in qualche modo modificato il cambiamento non si propaga anche dopo la chiamata.
<?php
/* funzione di esempio */
function modifica_i_parametri($p1, $p2)
{
$p1 = 10;
$p2 *= $p1;
}
/* variabili da modificare */
$a = 5;
$b = 7;
/* Chiama una funzione che tenta di modificare
i parametri ma $a e $b rimangono uguali anche dopo
la chiamata perché il passaggio è per valore */
modifica_i_parametri($a, $b);
?>
Ora come abbiamo visto questo può essere utile ma a volte capita di volere che anche la variabile sorgente risenta del cambiamento, come fare quindi?. Basta usare il passaggio per riferimento. Grazie all'operatore '&' è possibile passare una variabile alla funzione per riferimento. Se questo operatore viene messo nella dichiarazione della funzione l'operatore verrà passato sempre per riferimento mentre se inserito solo al momento della chiamata starà a noi decidere quando usarlo.
<?php
/* funzione che modifica sempre il suo parametro */
function metti10(&$p)
{
$p = 10;
}
/* questa funzione invece non riesce sempre */
function metti100($x)
{
$x = 100;
}
/* esempi */
$a = 9;
metti10($a); /* ora $a contiene 10 */
metti100($a); /* $a contiene ancora 10 */
mettiamo(&$a); /* ora $a contiene 100 visto che abbiamo specificato
il passaggio per riferimento */
?>
I programmatori sono una razza incredibilmente pigra se trovano una tecnica che da la possibilità di poter scrivere 3 linee di codice in meno rispetto a quello che facevano prima la adottano subito. Più avanti vi capiterà di dover scrivere funzioni che il 98 percento delle volte accetta sempre lo stesso valore in ingresso ma quel due percento vi impedisce di eliminare quel parametro cosi siete costretti ad inserire lo stesso parametro in continuazione.
Ma ecco che gli autori di PHP si sono inventati i valori di default. Potete infatti decidere di assegnare un valore di default ad ogni parametro della funzione quando la dichiarate. Quando andrete a chiamare la funzione potrete evitare di inserire questo parametro e il valore di default verrà usato. Ogni volta che inserirete un valore esplicitamente il valore di default verrà invece ignorato.
<?php
/* funzione che stampa il nome del web server */
function stampa_webserver($nome = "apache")
{
echo "il web server su cui sta girando questo script è $nome\n";
}
/* quale sarà l'output delle seguenti chiamate? */
stampa_webserver();
stampa_webserver();
stampa_webserver();
stampa_webserver("iis");
stampa_webserver();
stampa_webserver();
stampa_webserver("apache2");
stampa_webserver();
?>
Un'ultima precisazione su questa fantastica tecnica. Ricordate sempre di mettere tutti gli argomenti opzionali con valore di default dopo tutti gli altri argomenti obbligatori senza un valore di default. Anche se possibile non devono esserci argomenti non opzionali dopo il primo con valore di default quindi fate molta attenzione perché questo può portare ad errori incredibilmente subdoli e difficili da trovare.
<?php
/* funzione con alcuni parametri obbligatori e alcuni opzionali */
function funzione_giusta($a, $b, $c, $K = 10, $delta = 0.001)
{
/* fa qualcosa ... */
}
/* questa funzione è sbagliata.*/
function funzione_sbagliata($a, $tipo = "dV", $b, $ris)
{
/* fa altre cose */
}
/* Chiamate di esempio */
funzione_giusta(1,2,3);
funzione_giusta(1,2,3,1000);
/* in questo caso $b va in $tipo mentre $ris in $b. lasciando scoperto
la variabile obbligatoria $ris */
funzione_sbagliata($a,$b,$ris);
?>
Liste variabili di argomenti
Fino ad ora abbiamo sempre creato funzioni con una lista finita di funzioni. Questo è infatti il caso più comune ma a volte in alcuni rari casi potrete avere la necessita di passare un numero variabile di argomenti e di non conoscere neanche quanti questi possano essere. Per fare un esempio noto a molti la funzione printf del C è una di queste.
In PHP non dovete fare assolutamente niente visto che la dichiarazione della funzione rimane identica a quella precedente. Potete infatti inserire un numero a piacere, ma anche uguale a zero, di argomenti. Dopo di che potrete divertirvi chiamando la funzione con un numero di argomenti sempre diverso. La parte all'interno della funzione cambia invece un po'. Dovrete infatti usare le funzioni func_num_args(), che vi dirà quanti argomenti sono stati passati, e func_get_arg() o func_get_args() per ottenere gli argomenti veri e propri. La scelta è a vostra discrezione visto che fanno la stessa cosa. L'unica differenza è che la prima ritorna un solo argomento alla volta la seconda ritorna tutti gli un'array.
<?php
/* funzione che stampa la media tra due o più
argomenti */
function media($a, $b)
{
$tot = 0;
$elem = func_get_args();
for ($i = 0; $i < count($elem); $i++)
$tot += $elem[$i];
return (double)$tot / count($elem);
}
echo "la media tra 4 e 8 è:", media(4, 8);
echo "\nla media tra 4,5,60,7,14 è:", media(4,5,60,7,14);
?>
La ricorsione
Siamo finalmente giunti alla ricorsione. Croce e delizia del popolo dei programmatori. Per alcuni di voi sarà uno strumento utilissimo di cui non potrete fare a meno, per gli altri diventerà l'incubo che li sveglierà durante la notte.
Cominciamo col dare una definizione. In generale per ricorsione si intende quel meccanismo tramite il quale un qualcosa, in genere funzioni o entità matematiche, è espresso in termini di se stessa. Quindi un concetto ricorsivo è qualcosa che è definito su se stesso. Lo sono molte funzioni matematiche, algoritmi e strutture di dati. Per fare solo alcuni degli esempi più famosi basta citare l'MCM, il fattoriale, gli alberi e le grammatiche.
Alcuni potrebbero obbiettare che infondo algoritmi come il fattoriale sono intuitivi e semplici da realizzare anche con un ciclo. Il fatto è che non tutti gli algoritmi sono come quelli appena citati. Ne esistono alcuni che se scritti tramite la ricorsione risultano incredibilmente più veloci e il codice da scrivere è molto inferiore ad una implementazione iterativa.
Il problema è che non tutti riescono a pensare ad un algoritmo in modo ricorsivo. Questo porta a molti errori e ai loop che sono il più grande pericolo di chi scrive funzioni ricorsive perché una funzione quando entra in loop non riesce ad uscire dal ciclo e non termina mai bloccando di fatto il programma.
In genere è possibile suddividere una funzione ricorsiva in tre grandi blocchi. L'invariante, la terminazione e la progressione. L'invariante è quella condizione che indica quando si deve procedere con l'esecuzione della sequenza di funzioni ricorsive. La terminazione specifica che output deve dare la funzione quando non è valida la condizione dell'invariante. Ed in fine la progressione che descrive in che modo ottenere la nuova azione a partire dalla precedente. Una volta trovati questi tre macro blocchi è facile comporli per ottenere una funzione ricorsiva funzionante.
<?php
/* esempio di una funzione ricorsiva che calcola l'mcm di due numeri
in questo caso:
Invariante: $a > $b
Terminazione: $a == $b allora $a
Progressione: Se $a > $b allora $a - $b, $b altrimenti $a, $b - $a
*/
function mcm($a , $b)
{
return ($a == $b) ? $a : ($a > $b) ? mcd ($a - $b, $b) : mcd ($a, $b - $a);
}
?>
Ho volutamente usato un algoritmo semplice per spiegare. In questo modo spero di non aver spaventato nessuno. Per ora non andrò avanti con la ricorsione ma consiglio a tutti di cercare su internet documenti di approfondimento e a fare esercizi che vi aiuteranno di sicuro.