A funkcionális programozás egy olyan programozási forma ahol kerüljük a változók értékadását mert az a program belső állapotának megváltozását jelenti. A funkcionális programozásban egy $f(x)$ függvény mindig ugyan azt adja ugyan arra az $x$-re.
Ez nem teljesül, ha változóknak értéket adok majd azokat megváltoztatom, például az alábbi függvény más eredményt adhat akkor is, ha a paramétere ugyan az.
x=1
def f(y):
return x+y
print f(5)
x = -5
print f(5)
Ha ezt elkerüljük, akkor a függvényeink matematikai értelemben függvények lesznek, nem pedig eljárások (gépi utasítások) egymás utánjai. Innen a név, funkcionális programozás.
Guido van Rossum eredetileg ki kívánta dobni a funkcionális programozás alapelemeit a Python 3 core-ból a listaértelmezéssel helyettesíthetőnek tartva a fontosabbakat. A funkcionális programozás híveinek kemény ellenállását látva végül a lambda, map(), filter() maradt, de a reduce() kikerült az functools csomagba.
Ha egyszerű műveleteket szeretnénk végezni függvényekkel, akkor ezekhez külön függvényt írni felesleges:
def negyzet(x):
return x * x
negyzet(5)
Ehelyett egy függvény objektumot hozunk létre, majd amögé írunk zárójelet.
negyzet = lambda x: x*x
print negyzet(6)
(lambda x: x**3)(8)
A lambda-függvénynek több argumentuma is lehet:
osszeg = lambda a, b: a + b
osszeg(2, 4)
A map
egy lista minden elemére alkalmazza a megadott függvényt és visszaadja az így kapott listát. Formulával valahogy így nézne ki:
$$map(f, L) = \{f(x) \mid x\in L\}$$
Ha $f: A\mapsto B$ akkor $map:(A\mapsto B) \times A^n\mapsto B^n$ ($n\in \mathbb{N}$)
L = [1, 5, 8]
print map(negyzet, L)
Lambda fügvénnyel is használható.
print map(lambda x: x**2, range(5))
A map
többváltozósként is használható. Ha az $f$ függvény több változós, akkor több lista megadásá szükséges és
a map
mindegyiken szimultán halad és hattatja az $f$ függvényt.
Formalizálva valahogy így: $$map: (A \mapsto X), A^n \mapsto X^n$$ $$map: (A\times B \mapsto X), A^n, B^n \mapsto X^n$$ $$map: (A\times B \times C\mapsto X), A^n, B^n, C^n \mapsto X^n$$ $$\vdots$$
map(pow, [0.5, 1, 1.5], [-1, 2, 3])
Ha elágazást (if) akarunk használni egy lambda függvényben, azt így tehetjük meg.
"képlet igaz feltétel esetén" if feltétel else "képlet hamis feltétel esetén"
abszolut = lambda x: x if x>=0 else -x
print abszolut(5)
print abszolut(-5)
map(lambda x: x if x>=0 else 0, range(-5,5))
Ez még csak nem is lenne érdekes, mert if
nem-funkcionális (ú.n procedurális) programozásban is volt.
Szóval ez most csak egy másik szintaxis ugyan arra.
De ebben az if
-ben nem tudunk nem else
ágat rakni.
Ennek egy következménye, hogy nem lesznek lefedetlen esetek a kódban.
Példa lefedetlen esetekre:
def korosztaly(x):
if x < 8:
return "gyerek"
elif x < 15:
return "fiatal"
elif x >=18:
return "felnott"
print korosztaly(16)
def korosztaly(x):
return "gyerek" if x<8 else ("fiatal" if x<15 else ("felnott" if x>=18 else "INVALID"))
print korosztaly(16)
A filter
függvény leszűr egy listát egy adott bool-függvény szerint.
A feltétel függvény egy $A\mapsto \{True, False\}$ függvény kell legyen.
$$filter : (A\mapsto \{True, False\})\times A^n\mapsto A^m \text{ ahol } m\leq n$$
print filter(lambda x: x % 2 == 0, range(10))
print filter(str.isupper, "aAbBcC")
A reduce
egy 2-változós függvényt alkalmaz egy iterálható objektum (pl. lista) elemeire balról jobbra haladva, míg végül egyetlen értéket nem kap. Alakja:
reduce(*függvény*, *iterálható*[, *kezdő_érték*])
A kétváltozós függvény $A\times B\mapsto A$ alakú kell legyen az iterálható elemek $B$ beliek, a kezdőérték $A$-beli. Ha nincs kezdőérték, akkor $0$-nak tekinti.
Egy $(a, b, c)$ tuple-re és egy $x$ kezdőértékre a reduce
a következőt adja:
$$reduce(f, (a,b,c), x) = f(f(f(x, a), b), c)$$
osszeg = lambda a, b: a+b
print reduce(osszeg, [1, 2, 3, 4])
osztas = lambda a, b: a/b
print reduce(osztas, [1, 2, 3, 4], 1.0)
print 1/24.0
Ezzel az összegzés tétele elvileg bármikor elvégezhető és a szélsőérték keresés ennek egy alesete.
print reduce(max, [0,1,10,-10, 2], float("-inf"))
print reduce(min, [0,1,10,-10, 2], float("inf"))
Vagy ha nagyon szeretnénk villogni:
print reduce(lambda x, y: x if x > y else y, [0,1,10,-10, 2], float("-inf"))
A skalár (belső) szorzat egy implementációja.
def skalar(a, b):
return sum(map(lambda x, y: x*y, a, b))
print skalar([0.5, 1, 1.5], [-1, 1, -1])
Általában az $f(g(x_1, y_1), g(x_2, y_2), \ldots g(x_n, y_n))$ akalú kifejezéseket lehet a skalár szorzás általánosításának nevezni.
Könnyű implementálni funkcionálisan.
def inner(a, b, g=lambda x, y: x*y, f=sum):
return f(map(g, a, b))
print inner([0.5, 1, 1.5], [-1, 1, -1])
print inner("abc", [1,3,2], f=list)
Vagy ha a $g$ függvény asszociatív, akkor lehet két változósnak is tekinteni és reduce-al számolni.
def inner_2(a, b, g=lambda x, y: x*y, f=lambda a, b: a+b, i=0):
return reduce(f, map(g, a, b), i)
print inner_2([0.5, 1, 1.5], [-1, 1, -1])
print inner_2("abc", [1,3,2], i='')