Potreba za funkcionalnim programiranjem

Share

Izazovi u harderskoj arhitekturi su postali softverski problem

Poslednjih nekoliko decenija svedoci smo konstantnog unapređenja računarskih sistema u brzini, efikasnosti, povećanju skladišnog prostora i trenda smanjivanja veličina samih komponenata.

Popularni Murov zakon, koji predviđa da se snaga računara udvostručuje na otprilike svake dve godine, se ispostavio kao tačan i kao takav važi od sedamdesetih godina prošlog veka. Ali, postoji problem da povećanje broja tranzistora na čipu i radne frekfencije procesora zahteva eksponencijalno više energije za rad.

MIkroprocesori su originalno razvijeni da imaju jednu procesorsku jedinicu, tj. jedan “core”. Oko 2005. godine, kako bi bili u mogućnosti da kontinuirano unapređuju perfromanse procesora za opšte namene, proizvođači mikroprocesora kao što su Intel i AMD su prešli na tzv. “multi-core” arhitekturu.

Grafik

Podaci od Kunle Olukotun, Lance Hammond, Herb Sutter, Burton Smith, Chris Batten i Krste Asanovic

Tradicionalno, kompjuterski programi su pisani sa namenom da se izvršavaju serijski, instrukciju po instrukciju, koje su izvršavane jedna za drugom. Kako bi bili u mogućnosti da iskoristimo više od jednog core-a procesora, javila se potreba za nekim pristupom koji bi omogućavao paralelno izvršavanje. I tako, skoro preko noći, zbog jedne promene koju su uveli proizvođači hardvera, pristup razvoju softvera je morao biti preispitan.

Da stvari budu još komplikovanije za ljude koji se bave razvojem softvera, očekuje se da će Murov zakon nastaviti da važi i u narednom periodu. S tom razlikom da će se, umesto konstantnog povećanja procesorskog takta (frekfencije), snaga računara povećavati preko dodatnog povećanja broja core-ova na jednom procesoru.

Paralelno i konkurentno programiranje je komplikovano

Paralelni i konkurentni način programiranje se u osnovi razliku. Paralelno programiranje predstavlja brže izvršavanje programa na paralelnom hardveru. S druge strane, konkurentno programiranje je zapravo ekslipcitno upravljanje izvršenjem konkurentnih niti (thread-ova). Ipak, oba ova pristupa imaju nešto zajednočko – oba su komplikovana i teška za razvoj i održavanje.

Problem sa konkrurentim pristupom je u tome što konkurentni niti uzrokuju da rezultat izvršenja jedne funkcije može da se razlikuje od jednog do drugog poziva. Kako bi bili sigurni da će za iste ulazne argumente funkcija uvek vraćati isto rešenje, trebalo bi da vodimo računa da ne promenim vrednost objekata koji se izvršavanju u paraleli, tj. izbegavanje mutiranja statusa objekta (mutable state). Izbegavanje mutiranja objekata zapravo predstavlja funkcionalno programiranje. Korišćenjem funkcija možemo mnogo lakše pisati programe koji se izvršavaju u paraleli i samim tim su bolje prilagođenji multi-core sistemima.

Funkcionalno programiranje

Funkcionalno pogramiranje je jedna paradigma, tj. obrazac u programiranju. Ono je već decenijama popularno u akademskim krugovima. U poslednjih 10-ak godina, funkcionalno programiranje počinje da se primenjuje i u komercijalnim IT firmama, takođe. Verovatno ste u skorije vreme bili u prilici da čujete za programske jezike kao što su Scala, F#, Closure itd. Reaktivno programiranje, koji se bazira na gradivnim blokovima funkcionalnog programiranja, takođe je veoma popularno u poslednje vreme.

Funkcionalno programiranje se smatra kao deo tzv. deklarativnog načina programiranja. U poređenju sa imperativnim načinom programiranja, gde kod predstavlja kako bi trebalo nešto uraditi, kod deklativnog pristupa kod predstavlja šta bi trebalo da se uradi, bez neophodno i specificiranja načina “kako” bi trebalo da bude urađeno. Funkconalno programiranje omogućavi viši nivo abstrakcije u pisanju programa, bolji “re-usabiliti” tj. ponovno iskorišćenje koda, manje dupliciranog koda (DRY princip, don’t-repeat-yourself), “lazy” evalacija, tj. evaluiranje funkcijskog koda samo kada se on koristi, lakše programiranje paralelnog izvrašavanja programa itd. Iako modelni programski jezici nisu svi u osnovi funkcionalni, većina danas pruža mogućnost pisanja koda na deklarativan način. Ali, kako bi prešli sa imperativnog na deklarativni način pisanja softvera, moramo da promenimo način razmišljanja i pristupa samim programskim rešenjima. Ovaj prelazak ne predstavlja samo promenu u sintaksi jezika već mnogo šire – promenu u semantici jezika (kako se problemima pristupa i na koji način se oni rešavaju).

Prilagođavanje softverskih timova na funkcionalno programiranje nije tako lak proces. Razlozi za to najverovatnije dolaze od činjenice da je funkcionalno programiranje, samo po sebi, u osnovi komplikovanije od tradicionalnog strukturalnog programiranja. Takođe, ono sa sobom nosi i viši nivo abstrakcije u kôdu. Ali, trebalo bi uzeti u obzir i još jedan vrlo bitan aspekt, a to je da je većina nas koji se danas bavimo programiranjem, tokom celokupnog školovanja i dosadašnjeg rada naučena da razmišlja i implementira algoritme na strukturalni, tj. imperativni način. Ovo je svakako slučaj u Srbiji, ali verujem da se ne razlikuje mnogo ni na globalnom nivou. Kada nešto radite dovoljno dugo, to onda pređe u naviku. A navike se teško menjaju.

Promena perspektive

S druge strane, ne bi trebalo da promena perspektive i pisanja programa koristeći deklarativni način bude toliko teška. Na primer, SQL upiti za baze podataka, koje svi koristimo od početka našeg bavljenja programiranjem, u stvari predstavljaju primer deklarativnog programiranja. Pogledajmo sledeći primer:

SELECT e.name

   FROM employees e JOIN positions p ON e.positon_id = p.id

 WHERE p.name = 'SENIOR'

   AND e.age > 40;

Ovaj izraz “kaže” bazi da nam vrati imena svih zaposlenih koji imaju relaciju na poziciju koja se zove ‘SENIOR’ i koji su stariji od 40 godina. Prilično jednostavno i koncizno.

Primetimo da ovde NE kažemo bazi da uradi sledeće:

  • otvori tabelu employees
  • za svaki slog u employees tabeli uradi sledeće:
    • otvori positions tabelu i prođi kroz sve slogove u njoj, pokušavajući da pronađeš id kolonu koja se poklapa sa kolonom employees.position_id
    • ukoliko postoji indeks kreiran nad kolonom positions.id koristi ga za brze pretraživanje
    • ukoliko je odgovarajući slog u tabeli positions pronađen, sačuvaj ga zajedno sa odgovarajućim slogom iz tabele employees, i pripremi za vraćanje
    • ako kolona positions.id ima jedinstvene (unique) vrednosti, pređi na sledeći slog iz tabele employees: u suprotnom nastavi dalje sa ostalim slogovima iz tabele positions
  • filtriraj pronađene rezultate i zadrži samo one gde je positions.name = ‘ SENIOR’ i broj godina (employees.age) veći od 40
  • nastavi sa sledećim slogom iz employees tabele
  • kada su svi slogovi provereni, vrati employees.name za svaki pronađeni slog

Naša baza je dovoljno pametna da odredi ovo sama i odluči koji je najefikasniji način da pronađe i vrati nam ono što smo od nje zahtevali (koristeći naš SQL kod).

Java 8 primer

Kako bi ilustrovali razliku između imperativnog i funkcionalnog (deklarativnog) programiranja, pogledajmo primer koji koristi poslednju verziju Jave 8 (ali ovog puta u obrnutom smeru).

Standardan pristup, u prethodnim verzijama Jave, bio bi imperativni način:

public Set<Employee> findSeniors(Set<Employee> employees) {

    Set<Employee> result = new HashSet<>();

    for (Employee e : employees) {

        if (e.getPostion().equals(Postion.SENIOR)

                && e.getAge() > 40) {

            result.add(e);

        }

    }

    return result;

}

Koristeći nove funkcionalnosti Jave 8, kao što su lambda izrazi, metod reference i Stream API, imamo mogućnost da koristimo deklarativni pristup u aplikacionom kodu, takođe:

public Set<Employee> findSeniors(Set<Employee> employees) {

    return employees.stream()

        .filter(e -> e.getPosition().equals(Postion.SENIOR))

        .filter(e -> e.getAge() > 40)

        .map(Employee::getName)

        .collect(toSet());

}

Kod je mnogo čitljiviji i jednostavniji za razumevanje. Takođe, na vrlo jednostavan način je moguće ovaj kod izvršiti koristeći više paralelnih niti (thread-ova).

Zaključak

Evolucija u razvoju programskih jezika prethodnih godina omogućava pisanje aplikacionog koda koristeći funkcionalni, tj. deklarativni način. Kompajler i/ili virtuelna mašina samog programskog jezika u tom slučaju odlučuje kako da deklarisani kod izvrši. Paralelno programiranje je i dalje prilično kompleksan zadatak. Ipak, ljudi koji razvijaju same programske jezike se trude da pronađu sve bolje i sve efikasnije načine kako da iskoriste resurse multi-core sistema. Ali, kako bi sve to moglo da zaživi u stvarnosti, mi, kao programeri, bi trebalo da prestanemo sa “mikro menadžmentom” svega u kodu koji pišemo – tako što mu govorimo do detalja kako nešto da uradi. Umesto toga, trebalo bi da se fokusiramo na to šta bi naš program trebalo da radi. Naravno, ovo je lakše reći nego učiniti, ali bi trebalo da počnemo da uzimamo u obzir funkcionalni način razvoja softvera.

Share

Prijavi se da prvi dobijaš nove blogove i vesti.

Оставите одговор

Dražen Nikolić

Senior Software Architect @enjoy.ing
mm

Software development enthusiast with 15 years of experience implementing Java solutions. Working as Senior Software Architect at enjoy.ing. Over years also involved into various leadership and management positions, but his true nature is in engineering and making stuff that works… in the most efficient and elegant way possible, together with a colleagues and friends who share the same goal.

Prijavi se da prvi dobijaš nove blogove i vesti.

Категорије