A funkcionális programozás alapelemei

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.

Történelem

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.

Lambda függvények

Ha egyszerű műveleteket szeretnénk végezni függvényekkel, akkor ezekhez külön függvényt írni felesleges:

In [1]:
def negyzet(x):
    return x * x
negyzet(5)
Out[1]:
25

Ehelyett egy függvény objektumot hozunk létre, majd amögé írunk zárójelet.

In [2]:
negyzet = lambda x: x*x
print negyzet(6)

(lambda x: x**3)(8)
36
Out[2]:
512

A lambda-függvénynek több argumentuma is lehet:

In [3]:
osszeg = lambda a, b: a + b
osszeg(2, 4)
Out[3]:
6

map

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}$)

In [4]:
L = [1, 5, 8]
print map(negyzet, L)
[1, 25, 64]

Lambda fügvénnyel is használható.

In [5]:
print map(lambda x: x**2, range(5))
[0, 1, 4, 9, 16]

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$$

In [6]:
map(pow, [0.5, 1, 1.5], [-1, 2, 3])
Out[6]:
[2.0, 1, 3.375]

Elágazás (if)

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"

In [7]:
abszolut = lambda x: x if x>=0 else -x
print abszolut(5)
print abszolut(-5)
5
5
In [8]:
map(lambda x: x if x>=0 else 0, range(-5,5))
Out[8]:
[0, 0, 0, 0, 0, 0, 1, 2, 3, 4]

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:

In [9]:
def korosztaly(x):
    if x < 8:
        return "gyerek"
    elif x < 15:
        return "fiatal"
    elif x >=18:
        return "felnott"

print korosztaly(16)
None
In [10]:
def korosztaly(x):
    return "gyerek" if x<8 else ("fiatal" if x<15 else ("felnott" if x>=18 else "INVALID"))

print korosztaly(16)
INVALID

filter

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$$

In [11]:
print filter(lambda x: x % 2 == 0, range(10))
[0, 2, 4, 6, 8]
In [12]:
print filter(str.isupper, "aAbBcC")
ABC

reduce

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)$$

In [13]:
osszeg = lambda a, b: a+b
print reduce(osszeg, [1, 2, 3, 4])
10
In [14]:
osztas = lambda a, b: a/b
print reduce(osztas, [1, 2, 3, 4], 1.0)
print 1/24.0
0.0416666666667
0.0416666666667

Ezzel az összegzés tétele elvileg bármikor elvégezhető és a szélsőérték keresés ennek egy alesete.

In [15]:
print reduce(max, [0,1,10,-10, 2], float("-inf"))
print reduce(min, [0,1,10,-10, 2], float("inf"))
10
-10

Vagy ha nagyon szeretnénk villogni:

In [16]:
print reduce(lambda x, y: x if x > y else y, [0,1,10,-10, 2], float("-inf"))
10

Érdekesség

A skalár (belső) szorzat egy implementációja.

In [17]:
def skalar(a, b):
    return sum(map(lambda x, y: x*y, a, b))
print skalar([0.5, 1, 1.5], [-1, 1, -1])
-1.0

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

In [18]:
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)
-1.0
['a', 'bbb', 'cc']

Vagy ha a $g$ függvény asszociatív, akkor lehet két változósnak is tekinteni és reduce-al számolni.

In [19]:
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='')
-1.0
abbbcc