{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Jedna z (volnějších) definic *funkcionálního programování* říká, že *pokud nahradíme v kódu volání funkce výsledky tohoto volání, je program (jazyk) funkcionální*. Jinými slovy, funkcionální programování znamená, že volaní funkce pouze produkuje výstup, nemá žádné vedlejší účinky a ani nemění její interní stav. Funkcionální program je v podstatě rekurzivním vyhodnocením výrazů. Mezi funkcionální jazyky patří např. Haskell, Clojure, Lisp nebo Erlang. Funkcionální programování se řadí mezi *deklarativní* programovací techniky (kam patří např. i SQL) a je téměř opakem *objektově orientovaného programování*, kde volání metod mění stav objektů. Python je spíše objektové orientovaný jazyk, nicméně je v něm možné používat technik funkcionálního programování a jednodušše tyto dva odlišné způsoby programování kombinovat.\n", "\n", "\n", "A proč vlastně programovat funkcionálně? Jednoduchá, či spíše zjednodušující, odvěď je *vyhnout se vedlejším efektům*. To má své výhody, předvším jednoduchochou paralelizaci a vyhýbání se chybám typu přiřazení špatné hodnoty do proměnné. Zatím to ovšem vypadá, že čistě funkcionální programování nepřináší až tak velký užitek, neboť mezi programátory není příliš rozšířené. Vhodně použité funkcionální prvky v jinak procedurálním, objektově orientovaném jazyku mohou být velice užitečné. A toto nám Python poměrně dobře umožňuje.\n", "\n", "Více např. na http://docs.python.org/3/howto/functional.html\n", "\n", "## Základní kameny funkcionálního programování\n", "\n", "* First-class functions, anonymní (lambda) funkce\n", "* Nemění se stav (programu)\n", "* Immutable data\n", "* Closure (uzávěr)\n", "* Rekurze\n", "* Řady\n", "* ...\n", "\n", "## Podpora v Pythonu\n", "\n", "* Funkce `map`, `reduce`, `filter`\n", "* Zkrácené logické výrazy\n", "* Generátorová notace (pro tuple, list, set, dict)\n", "* Iterátory\n", "* Moduly `operator`, `itertools`, `functools`\n", "\n", "Pojďme se na to podívat prakticky." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Vyhýbáme se blokům if - else blokům a smyčkám\n", "\n", "Tento kód:\n", "\n", " if :\n", " func1()\n", " elif :\n", " func2()\n", " else:\n", " func3()\n", "\n", "je ekvivalentní\n", "\n", " ( and func1()) or ( and func2()) or (func3())\n", "\n", "Příklad:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "other\n" ] } ], "source": [ "def pr(s): \n", " return s\n", "\n", "x = 3\n", "res = (x==1 and pr('one')) or (x==2 and pr('two')) or (pr('other'))\n", "print(res)" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "two\n" ] } ], "source": [ "x = 2\n", "res = (x==1 and pr('one')) or (x==2 and pr('two')) or (pr('other'))\n", "print(res)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Pozor*: v tomto příkladu může dojít k nesprávnému vyhodnocení -- přijdete na to v jakém případě? (Nápověda: souvisí to s funkcí pr, resp. její návratovou hodnotou.) Lepší je proto použít [podmíněné výrazy (conditional expression)](http://docs.python.org/2/reference/expressions.html#conditional-expressions) (ternární logické operátory)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While smyčka může být nahrazena rekurzí:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "n = 2\n", "n = 3\n", "n = 4\n", "n = 5\n", "výsledek je 5\n" ] } ], "source": [ "n = 1\n", "while n < 5:\n", " n += 1\n", " print(\"n = {}\".format(n))\n", "print(\"výsledek je {}\".format(n))" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n", "3\n", "4\n", "5\n", "výsledek je 5\n" ] } ], "source": [ "# to samé rekurzivně\n", "def get_n(n=1):\n", " n += 1\n", " print(n)\n", " # použijeme podmíněný výraz\n", " return n if n >= 5 else get_n(n)\n", "print(\"výsledek je {}\".format(get_n()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## First-class funkce, uzávěry, lambda\n", "Pokud je možné funkce používat ekvivalentně jako proměnné, např. předávat je jako parametry funkcím nebo vracet funkce jako výsledek jiné funkce, mluvíme o first-class funkcích. S tímto konceptem se vážou i anonymní neboli lambda funkce. Opět to ukážeme na příkladech." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "# definujeme funkci foo\n", "def foo(x):\n", " return bool(x)\n", "\n", "# a přiřadíme do proměnné boo\n", "boo = foo" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "the result is: False\n" ] } ], "source": [ "def simply_apply(func, val):\n", " # v proměnné func předpokládáme funkci\n", " # simply_apply je \"higher-order function\"\n", " print(\"the result is: %s\" % str(func(val)))\n", "# nyní použijeme funkci simply_apply pro vyhodnocení jiné funkce\n", "# (konkrétně funkce foo, kterou máme v proměnné boo\n", "simply_apply(boo, ()) # všiměte si i druhého argumentu" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Funkci lze vytvořit v rámci jiné funkce a vrátit jako její výsledek. Tato funkce může obsahovat i uzávěr (closure), který zachová aktuální kontext." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def calculations(a, b):\n", " # tady se vytvoří uzávěr, aktuální hodnoty a, b se uchovávají ve funkci add\n", " def add():\n", " return a + b\n", "\n", " return add" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3\n", "5\n" ] } ], "source": [ "f = calculations(1, 2)\n", "print(f())\n", "f = calculations(3, 2)\n", "print(f())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vytvořená funkce `add` může mít i parametr." ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def calculations(a, b):\n", " def add(x):\n", " return x * (a + b)\n", "\n", " return add" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6\n", "14\n" ] } ], "source": [ "f12 = calculations(1, 2)\n", "f34 = calculations(3, 4)\n", "print(f12(2))\n", "print(f34(2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pro vyvoření nové funkce můžeme také použít anonymní (lambda) funkci." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def calculations(a, b):\n", " return a, b, lambda x: x * (a + b)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6\n", "14\n" ] } ], "source": [ "a, b, f = calculations(1, 2)\n", "aa, bb, ff = calculations(3, 4)\n", "print(f(2))\n", "print(ff(2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Higher ordered functions (neboli funkcionály) přijímají jako parametr funkci." ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "def calc(f):\n", " return lambda x: f(x + 1)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ ". at 0x7fdfaa5c2680>\n", "9\n" ] } ], "source": [ "foo = calc(lambda x: x**2)\n", "print(foo)\n", "# výsledná funkce je (x + 1)**2\n", "print(foo(2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Konec hraní, používáme závorky, map, reduce, filter\n", "\n", "Jak už jsme ukázali [dříve](http://www.pythonic.eu/fjfi/posts/iteratory-a-generatory.html), Python obsahuje koncept iterátorů, který je uplatněn pro vestavěné kontejnery, jako jsou `list`, `tuple`, `dict`, `set`, a objevuje se i u datových toků (stream), tedy např. souborů. Dále Python obsahuje *generátory* a *generátorovou notaci*. Iterátory, jakožto metody objektů, mohou ovšem měnit vnitřní stav, proto úplně nezapadají do funkcionálního stylu programování." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Generátorová notace\n", "Pomocí závorek a několika klíčových slov `for` a `in`, případně také `if`, můžeme jednoduše vytvářet kontejnery." ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/plain": [ "[1, 2, 3, 4]" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# list pomocí [ ... ]\n", "[i+1 for i in [0, 1, 2, 3]]" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/plain": [ " at 0x7fdfaa452c50>" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# generator (nikoli tuple) pomocí ( ... )\n", "(i+1 for i in [0, 1, 2, 3])" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "3\n" ] } ], "source": [ "# generátor je iterable\n", "for i in (i for i in range(4) if i % 2):\n", " print(i)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/plain": [ "{0: 1, 1: 2, 2: 3, 3: 4, 4: 5}" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# dict se tvoří pomocí { ... }\n", "{i: i+1 for i in range(5)}" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "data": { "text/plain": [ "{0, 1, 2, 3, 4}" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# a set také pomocí { ... }\n", "{k for k in {i: i+1 for i in range(5)}.keys()}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### map -- reduce (-- filter)\n", "\n", "Map -- reduce princip je velice rozšířený ve funkcionálním programování a hojně se také využívá v (paralelním) zpracování dat, především pokud je jich mnoho. V Pythonu máme funkce `map` a `reduce`, které moho být aplikovány na jakékoli iterable obejkty.\n", "\n", "* `map(function, object)` - aplikuje funkci function postupně na prvky object (pomocí iterací). function musím mít jeden argument (další mohou být nepovinné). Vrátí iterátor výsledků -- `map` objekt.\n", "* `functools.reduce(function, object)` - aplikuje funkci kumulativně na dva prvky obejktu, výsledkem je *jedna hodnota* podledního volání funkce. Reduce jde zapsat rekurzivně jako f[1] = function(object[0], object[1]), f[n+1] = function[f[n], object[n+1]). \n", "* `filter(function, object)` vrátí elementy, pro které je funkce True.\n", "\n", "Ukážeme si to na příkladech." ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n" ] } ], "source": [ "from operator import ifloordiv\n", "\n", "# aplikujeme funkci str\n", "print(map(str, range(10)))\n", "\n", "# aplikujee lambda funkci, která vrací celočíselné dělení 3\n", "print(map(lambda x: ifloordiv(x, 3), range(10)))" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0123456789\n", "12\n" ] } ], "source": [ "from operator import add\n", "from functools import reduce\n", "\n", "# teď \"zredukujeme\" předchozí výsledky pomocí add (sčítání)\n", "print(reduce(add, map(str, range(10))))\n", "print(reduce(add, map(lambda x: ifloordiv(x, 3), range(10))))" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "# čísla dělitelná třemi\n", "print(filter(lambda x: (x % 3) == 0, range(10)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Většinou se v Python komunitě doporučuje používat závorky neboli generátorovou notaci:\n", "\n", "* místo `map` použijeme `(function(x) for x in object)`\n", "* místo `filter` máme `(x for x in object if function(x))`\n", "* místo reduce můžeme v mnoha případech použít vestavěné `sum`, `all`, `any`" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']\n" ] } ], "source": [ "print([str(x) for x in range(10)])" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 3, 6, 9]\n" ] } ], "source": [ "print([x for x in range(10) if (x % 3) == 0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Cvičení\n", "\n", "\n", "1. Naprogramujte generátorovou funkci `slowmap(func, iterable, speed=1)`, která bude dělat to samé jako `map(func, iterable)`, ale mezi každou iterací bude čekat pomocí funkce `time.sleep` `1/speed` vteřin. \n", "2. Vyzkoušejte pomocí `slowmap` aplikovat lambda funkci, která vrací třetí mocninu argumentu, na `range(10)`. Výsledek nakonec uložte do proměnné typu `list`.\n", "3. Napište funkci `pown(n)`, která bude vracet funkci vracející n-tou mocninu argumentu. Tj. např. `pown(3)(2)` vrátí výsledek `8`. Tuto funkci použijte místo lambda funkce v části 2." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 }