Functional programming for noobs

Usate principi di programmazione funzionale per lavoro? Vi divertite? Pensate di smettere?



Con la certezza che questo sarà l'unico post del thread, comincio io!



Lavorando principalmente in c#, java/kotlin e typescript, ho cominciato ad introdurre con estrema soddisfazione immutabilità, funzioni pure e tipi precisi per ogni cosa; ognuna di queste cose si sta rivelando una vittoria notevole in termini di leggibilità del codice e manutenibilità, in alcuni casi, di performances.


Unico problema, tra l'introduzione di questi concetti nei team dove entro al ringraziamento finale dei programmatori che cominciano a usarli, mi trovo odiato a ogni merge request. Avete vissuto qualcosa del genere?
Scusa odiato perché, codice diverso dal solito? Troppe diff/dimensioni del PR? Cambiamenti non relati al task/sottosistema che dovevi modificare/estendere? Troppo tempo perso nel refactoring? Cambi codice non coperto da test?

Alcune sono legittime altre no difficile da dire
Semplicemente un modo diverso di lavorare. Son più di dieci anni che c# ha le lambda e la monade ienumerable con select many, ma ancora c'è gente che non comprende il perché una select é più potente di un for pur essendo cento volte meno duttile dal loro punto di vista.
Quando durante il pairing o le mr gli dico di usare una reduce o fold o aggregate, il programmatore che non ha voglia di imparare un paradigma nuovo non capirà mai il valore di una pure function o dellimmutabilita. Semplicemente comincia a tirare i remi in barca, credendo che quanto ha fatto finora sia messo in discussione. Si sente aggredito insomma.
Un disclaimer é d'obbligo comunque, oramai sono 6 anni che faccio tutoraggio, quindi ho pure un sacco di soddisfazioni, sto parlando di quei casi intermedi, o di quando trovo particolari resistenze da programmatori ai quali importa poco la qualità o la tecnica.
Vabbe allora cxxi loro
Noi abbiamo "risolto" con un documento riguardo stile e best practices; l'originale l'ho creato io ma tutti possono modificarlo via PR.
Per dire, se qualcuno scrive un for che è coperto da uno degli algoritmi in stl lo si commenta in PR "read the style guide".
Deve cmq partire da sopra (tuo team lead, coordinator, o come lo chiamate voi ) e concordato, una volta deciso cosa "è ok" tutti si devono conformare se vogliono che le PR passino o avere una giustificazione per deviare.

Il grosso delle cose indicate nella guida viene anche segnalato da cppcheck e clang-tidy quindi a breve abbiamo intenzione di integrarli nelle PR.


Premesso che non sono un dev, ma un descrizione breve di cosa è la programmazione funzionale?

Giusto per restare informato....
Posso solo rendere peggio la spiegazione di Wikipedia ma ci provo:
La programmazione funzionale é un paradigma che si affianca/sostituisce a quello imperativo/ad oggetti dove sono le funzioni e la loro composizione la priorità, al posto delle classi/oggetti.
In pratica ci si riferisce per lo più ad un modo di pensare ed affrontare i problemi differente e più vicino alla matematica (o ad una branca della matematica, per la precisione la teoria delle categorie).
Di recente moltissimi linguaggi ad oggetti si sono avvicinati alla programmazione funzionale introducendo piccole/grandi modifiche, ma ci sono molti altri concetti che stanno sotto a questo ombrello della PF, e genericamente sono difficili da proporre ai programmatori che meno vogliono aggiornarsi..
A lavoro ho cominciato un book club dove leggiamo functional programming in kotlin della manning. Sembra funzionare come approccio per insegnare ai junior senza dover far di continuo pair programming, e consiglio un sacco il libro: è un evoluzione del libro su scala, ha un fracco di esercizi utili e non parte in maniera troppo aggressiva.
Functional programming funziona quando usi un linguaggio totalmente costruito attorno a functional programming o quando applichi qualche concetto senza andare full nazi FP.

Viceversa e' la classica epifania che hanno tutti i developer che a un certo punto scoprono FP pensando sia il sacro graal, cominciano a scrivere righe e righe di codice in FP in linguaggi che non sono nati per essere scritti in FP, finendo 3 mesi dopo a rileggere il loro codice di 3 mesi prima senza capire nulla.

Film gia' visto
Sinceramente sono quattro anni che continuo a lavorarci e sono stato fortunato a non cadere mai in quella trappola, principalmente in due aziende e nazioni totalmente differenti con scala, Kotlin e chiazze di f#, credo tu stia parlando di qualcosa di diverso, dato che i principi funzionali stanno diventando sempre più parte integrale di ogni linguaggio...

Tra poco ci vorrà più tempo a spiegare come fare codice imperativo che fare codice funzionale, basta stare lontani da un certo tipo di aziende e difficilmente si ritorna indietro...



A me spiegarono il concetto così e di lì in avanti ho sempre scritto codice a questa maniera, si parla di 12-10 anni fa.
Comunque, nemmeno io sono un dev: magari stimolo una risposta più corretta della mia

Contesto, Python (puoi testare sta roba su, boh, https://extendsclass.com/python.html):


global_list = []

def append_to_list(x):
global_list.append(x)

append_to_list(1)
append_to_list(2)
print(global_list)
Questa funzione non è scritta secondo il rigore della programmazione funzionale: vuole global_list definita, e, peggio ancora, ci scrive sopra senza che sia parte dei dati passati alla funzione.


Più idoneo sarebbe:


def append_to_list(x, list):
list.append(x)


newlist = []
append_to_list(1,newlist)
append_to_list(2,newlist)
print(newlist)
che opera come opererebbe una funzione matematica, in quanto prende elementi da un dominio (numero, lista) ed associa loro un codominio (lista contenente numero).
In python ci sono poi le funzioni lambda che sono un po' l'essenza della cosa: sono funzioni senza definizione che possono avere solo un'espressione. Tipo:

x = lambda a : a + 10
print(x(5))



Che permette di fare cose tipo:


def myfunc(n):
return lambda a : a * n


Ok, non ho fatto niente di speciale, ho definito una funzione con una lambda dentro? A che pro?
Ma sta a vedere...


def myfunc(n):
return lambda a : a * n

raddoppia = myfunc(2)
print(raddoppia(11))

triplica = myfunc(3)
print(triplica(11))


centuplica = myfunc(100)
print(centuplica(11))


calcolo_iva = myfunc(0.21)
print(calcolo_iva(11))



Meglio ancora con filter, map e reduce:


from functools import reduce



interi = [1,2,3,4,5,6]
dispari = filter(lambda n: n % 2 == 1, interi)
dispari_quadrati = map(lambda n: n * n, dispari)
totale = reduce(lambda acc, n: acc + n, dispari_quadrati)



print(total)
che è sta roba? Beh è l'equivalente di



interi = [1,2,3,4,5,6]
dispari = []
dispari_quadrati = []
total = 0


for i in interi:
if i%2 ==1:
dispari.append(i)


for i in dispari:
dispari_quadrati.append(i*i)


for i in dispari_quadrati:
totale += i


print(totale)
A me la fp piace parecchio, ma devo dire che spesso quando devo leggerla ci impiego 5 volte tanto il tempo che impiego a leggere e capire codice imperativo.

Inoltre, spesso chi usa la fp vedo che accorcia tutto l'accorciabile, con variabili e funzioni a singola lettera, esisterà un girone dell'inferno per questi?

Quando si insegna la fp un criterio fondamentale dovrebbe essere quello di impostare nomi di variabili e di funzione in maniera estesa e descrittiva, e nei punti più innestati e criptici buttarci un commento oneline giusto per far scontare al te futuro o ad altri quei 10 minuti in più per fare lo stack mentale e capire cosa sta facendo quella maledetta funzione chiamata "a" con parametri "b" e "c" dentro una funzione "d" etc....


Mediamente passiamo piu' tempo a leggere codice che a scriverlo, quindi questo dovrebbe essere un enorme red flag
Non so che linguaggi usate e che tipo di codebase leggiate ogni giorno, ma vi assicuro che sia in .NET che Java io con la PF ho aumentato di non poco la leggibilità:



  • la rimozione di eccezioni che interrompono il flow ha boostato qualunque team dove ho lavorato enormemente rendendo il codice più comprensibile
  • le linee di codice rimosse perché non ho bisogno d'infiniti mapper, builder e factory han reso la lettura un sacco più veloce
  • l'immutabilità mi ha permesso di sapere che l'unico punto dove viene creato quell'oggetto è l'unico punto dove il problema può essere nato
  • il denotational design mi ha permesso di scrivere codice che risolve problemi veri che parlano col linguaggio che uso col PO
  • higher order function ti permettono di concatenare pezzi di codice estremamente generici e riusabili e di buttare via enormi librerie (Spring è un abominio)
  • i costrutti comuni mi han permesso di muovermi in differenti linguaggi senza problemi, e un sacco dei programmatori ai quali ho insegnato ora sono full stack senza problemi, e tutti vogliono continuare FP

Unico problema? Similmente a git, devi studiarlo se vuoi capire quanto puoi fare senza avere mai problemi.


Gli esempi sono così tanti che non so da cosa partire, la sola foldRight, appena la impari, ti permette di costruire da zero un sacco di funzionalità senza rompere alcun principio SOLID, e senza compromessi, se usi un linguaggio che supporta tailrecursion, ovviamente.


Aggiornamento al mio post iniziale. Insistere ha dato i suoi frutti!
Ora l’intero programma (50 sviluppatori) sta usando molto di più la programmazione funzionale (Kotlin con la libreria arrow), e sto contagiando un team di un altro programma a fare un core f# altamente riusabile in c#.

Ho insistito buttandomi a pesce quando vedevo lunghe merge request con autori junior; ho mollato le speranze di contaminare i “senior” (programmatori anziani per la precisione, i senior veri non han avuto bisogno di convincimento) che irriducibili continuano a pensare spring sia l’unico salvatore. Facendo un sacco di pair programming specialmente con chi mostrava interesse sono riuscito di recente ad introdurre il saga pattern persino!
Ecco, condivisa una piccola storia felice.

Saga applicato a redux penso sia stato uno dei periodi più bui dello sviluppo frontend :asd:
Per fortuna è durato 2-3 anni e poi è finito nel dimenticatoio.

Lato backend ha senso per alcune tipologie di applicazioni, ad esempio molto potente nel settore finanziario, ma se applicato SEMPRE a qualsiasi situazione penso sia molto sbagliato

No saga redux, solo saga.

Sisi ho capito, il framework cambia ma il concetto è quello, rimango dell idea che sia adatto a casi specifici e se preso come “è sempre la cosa giusta da usare” possa portare a disastri, my 2 cents

Guarda il saga pattern è usato quotidianamente con altri nomi senza che la gente si scandalizzi o vada in ptsd (in questo caso per colpa dello stato miserabile del panorama frontend, fatto di tante piccole perle ma pure di hype cycle che portano alla tua esperienza).
Tanto per completezza faccio alcuni esempi di saga pattern che non vengono chiamati come tali ma sono usati giornalmente: @Transaction in spring, ogni versioning di database (flyway migrations ad esempio), un qualunque bump di msk su aws.

1 Like

Sì ci sta in quei casi, ma ad esempio come decidi quando applicare saga e quando non applicarlo? Io noto che la tendenza generale anche quando si parla di backend, è che introduci un nuovo tool e tutti si mettono a risolvere problemi con quel tool senza porsi troppe domande se sia quello giusto i sbagliato per una tipica situazione.

Hai qualche esempio nei tuoi casi specifici dove si è rivelato un ottima scelta in confronto ad altro pattern X?

Letteralmente l’ho usato per sostituirlo ad una implementazione manuale che cercava in modo insicuro di compensare una catena di post ad una api interna con delle delete. In questo caso quindi credo abbia buoni utilizzi quando sei nella stessa “area network” e sai che hai altissima reliability per tutti i componenti quindi il compromesso di tenere le operazioni in memoria è accettabile.
Già quando parliamo di integrazione a sistemi terzi per ottenere transazionalita a livelli affidabili userei un anti corruption layer con eventi in modo da ottenere la persistenza delle operazioni in corso (es kafka).

1 Like