Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Funkční programování je styl programování, který zdůrazňuje použití funkcí a neměnných dat. Funkční programování s typováním je kombinací funkčního programování se statickými typy, jako je F#. Obecně platí, že v funkčním programování jsou zdůrazněny následující koncepty:
- Funkce jako primární konstrukce, které používáte
- Výrazy místo příkazů
- Neměnné hodnoty u proměnných
- Deklarativní programování nad imperativním programováním
V této sérii prozkoumáte koncepty a vzory v funkčním programování pomocí jazyka F#. Kromě toho se naučíte také F#.
Terminologie
Funkční programování, podobně jako jiné programovací paradigmata, obsahuje slovní zásobu, kterou se nakonec budete muset naučit. Tady jsou některé běžné termíny, které uvidíte po celou dobu:
- Funkce – Funkce je konstrukce, která při zadání vstupu vytvoří výstup. Formálněji mapuje položku z jedné sady na jinou sadu. Tento formalismus je v mnoha ohledech uveden do praxe, zejména při použití funkcí, které pracují se sbírkami dat. Jedná se o nejzásadnější (a důležité) koncepty funkčního programování.
- Výraz – výraz je konstruktor v kódu, který vytváří hodnotu. V jazyce F# musí být tato hodnota vázána nebo explicitně ignorována. Výraz lze triviálně nahradit voláním funkce.
- Čistota - čistota je vlastnost funkce tak, aby její návratová hodnota byla vždy stejná pro stejné argumenty a že jeho vyhodnocení nemá žádné vedlejší účinky. Čistá funkce zcela závisí na jejích argumentech.
- Referenční transparentnost - Referenční transparentnost je vlastnost výrazů, které mohou být nahrazeny jejich výstupem, aniž by to mělo vliv na chování programu.
- Neměnnost – Neměnnost znamená, že hodnotu nelze změnit na místě. To je na rozdíl od proměnných, které se můžou změnit na místě.
Příklady
Následující příklady ukazují tyto základní koncepty.
Funkce
Nejběžnějším a základním konstruktorem funkčního programování je funkce. Tady je jednoduchá funkce, která přidá 1 do celého čísla:
let addOne x = x + 1
Signatura jeho typu je následující:
val addOne: x:int -> int
Podpis lze přečíst jako „addOne
přijme int
nazvaný x
a vytvoří int
“. Formálněji addOne
mapuje hodnotu ze sady celých čísel na množinu celých čísel. Token ->
označuje toto mapování. V jazyce F# se obvykle můžete podívat na podpis funkce, abyste získali představu o tom, co dělá.
Proč je podpis důležitý? V typovém funkčním programování je implementace funkce často méně důležitá než podpis skutečného typu! Skutečnost, že addOne
sčítá hodnotu 1 do celého čísla, je zajímavé během provádění programu, ale při vytváření programu rozhoduje skutečnost, že přijímá a vrací int
, o tom, jak tuto funkci skutečně použijete. Kromě toho, jakmile tuto funkci použijete správně (s ohledem na její typ podpisu), může být diagnostika jakýchkoli problémů provedena pouze v těle addOne
funkce. Toto je podnět pro typové funkční programování.
Výrazy
Výrazy jsou konstrukce, které se vyhodnotí jako hodnota. Na rozdíl od příkazů, které provádějí akci, lze výrazy považovat za provedení akce, která vrací hodnotu. Výrazy se téměř vždy používají v funkčním programování místo příkazů.
Vezměte v úvahu předchozí funkci, addOne
. Tělo addOne
je výraz:
// 'x + 1' is an expression!
let addOne x = x + 1
Je výsledkem tohoto výrazu, který definuje typ výsledku addOne
funkce. Například výraz, který tvoří tuto funkci, může být změněn tak, aby byl jiným typem, například string
:
let addOne x = x.ToString() + "1"
Podpis funkce je teď:
val addOne: x:'a -> string
Vzhledem k tomu, že na jakýkoli typ v jazyce F# může být ToString()
zavoláno, typ x
byl upraven na obecný (což se nazývá Automatická generalizace) a výsledný typ je string
.
Výrazy nejsou jen těla funkcí. Můžete mít výrazy, které vytvářejí hodnotu, kterou používáte jinde. Běžným je if
:
// Checks if 'x' is odd by using the mod operator
let isOdd x = x % 2 <> 0
let addOneIfOdd input =
let result =
if isOdd input then
input + 1
else
input
result
Výraz if
vytvoří hodnotu s názvem result
. Všimněte si, že můžete úplně vynechat result
, což způsobí, že výraz if
se stane tělem funkce addOneIfOdd
. Klíčovou věcí, kterou je potřeba pamatovat na výrazy, je, že vytvářejí hodnotu.
Existuje speciální typ unit
, který se používá, když není co vrátit. Představte si například tuto jednoduchou funkci:
let printString (str: string) =
printfn $"String is: {str}"
Podpis vypadá takto:
val printString: str:string -> unit
Typ unit
označuje, že se nevrací žádná skutečná hodnota. To je užitečné v případě, že máte rutinu, která musí "pracovat", i když nemá žádnou hodnotu, která by se v důsledku této práce vrátila.
To je v ostrém kontrastu s imperativním programováním, kde ekvivalentní if
konstrukce je příkaz, a vytváření hodnot se často provádí s měnícími se proměnnými. Například v jazyce C# může být kód napsán takto:
bool IsOdd(int x) => x % 2 != 0;
int AddOneIfOdd(int input)
{
var result = input;
if (IsOdd(input))
{
result = input + 1;
}
return result;
}
Je vhodné poznamenat, že jazyk C# a další jazyky ve stylu jazyka C# podporují ternární výraz, který umožňuje podmíněné programování založené na výrazech.
V funkčním programování je vzácné měnit hodnoty pomocí výrazů. I když některé funkční jazyky podporují příkazy a mutaci, není běžné tyto koncepty používat v funkčním programování.
Čisté funkce
Jak jsme už zmínili, čisté funkce jsou funkce, které:
- Vždy poskytne stejnou hodnotu při stejném vstupu.
- Nemá žádné vedlejší účinky.
V tomto kontextu je užitečné uvažovat o matematických funkcích. V matematice jsou funkce závislé pouze na jejich argumentech a nemají žádné vedlejší účinky. V matematické funkci f(x) = x + 1
závisí hodnota f(x)
pouze na hodnotě x
. Čisté funkce v funkčním programování jsou stejné.
Při psaní čisté funkce musí funkce záviset pouze na jejích argumentech a nesmí provádět žádnou akci, která má za následek vedlejší účinek.
Tady je příklad nečisté funkce, protože závisí na globálním, proměnlivém stavu:
let mutable value = 1
let addOneToValue x = x + value
Funkce addOneToValue
je jasně nečistá, protože value
je možné ji kdykoli změnit tak, aby měla jinou hodnotu než 1. Je třeba se vyhnout vzoru spoléhání se na globální hodnoty ve funkcionálním programování.
Tady je další příklad nečisté funkce, protože provádí vedlejší efekt:
let addOneToValue x =
printfn $"x is %d{x}"
x + 1
I když tato funkce nezávisí na globální hodnotě, zapíše hodnotu x
do výstupu programu. I když s tím není nic zlého, znamená to, že funkce není čistá. Pokud jiná část programu závisí na něčem externím programu, například na výstupní vyrovnávací paměti, může volání této funkce ovlivnit jinou část programu.
Odebráním printfn
příkazu je funkce čistá:
let addOneToValue x = x + 1
I když tato funkce není ze své podstaty lepší než předchozí verze s printfn
příkazem, zaručuje, že všechna tato funkce vrací hodnotu. Volání této funkce libovolný početkrát vytvoří stejný výsledek: pouze vytvoří hodnotu. Předvídatelnost daná čistotou je něco, po čem mnoho funkčních programátorů usiluje.
Neproměnlivost
A konečně, jeden z nejzákladnějších konceptů typového funkčního programování je neměnnost. V jazyce F# jsou všechny hodnoty ve výchozím nastavení neměnné. To znamená, že je nelze přímo změnit, pokud je explicitně neoznačíte jako mutabilní.
V praxi práce s neměnnými hodnotami znamená, že změníte svůj přístup k programování z " Potřebuji něco změnit", na "Potřebuji vytvořit novou hodnotu".
Například přidání 1 k určité hodnotě znamená vytvoření nové hodnoty, nikoli změnu původní hodnoty.
let value = 1
let secondValue = value + 1
V jazyce F# následující kód nezměnívalue
funkci; místo toho provede kontrolu rovnosti:
let value = 1
value = value + 1 // Produces a 'bool' value!
Některé funkční programovací jazyky vůbec nepodporují mutaci. V jazyce F# se podporuje, ale nejedná se o výchozí chování pro hodnoty.
Tento koncept se ještě více rozšiřuje o datové struktury. V funkčním programování mají neměnné datové struktury, jako jsou sady (a mnoho dalších), jinou implementaci, než byste mohli původně očekávat. Koncepčně, například přidání položky do sady, nezmění sadu, vytvoří novou sadu s přidanou hodnotou. Pod kryty je to často dosaženo jinou datovou strukturou, která umožňuje efektivní sledování hodnoty, aby bylo možné v důsledku toho dát odpovídající reprezentaci dat.
Tento styl práce s hodnotami a datovými strukturami je kritický, protože vás přinutí zacházet s jakoukoli operací, která něco upraví, jako by vytvořil novou verzi této věci. To umožňuje, aby ve vašich programech byly konzistentní věci, jako je rovnost a porovnatelnost.
Další kroky
V další části se důkladně zaměříme na funkce a prozkoumáte různé způsoby jejich použití v funkčním programování.
Použití funkcí v jazyce F# zkoumá funkce hlouběji a ukazuje, jak je můžete používat v různých kontextech.
Další četba
Série Myšlení funkčně je dalším skvělým zdrojem informací o funkčním programování pomocí jazyka F#. Zabývá se základy funkčního programování praktickým a snadno čitelným způsobem pomocí funkcí jazyka F#, které ilustrují koncepty.