F Sharp
F# (vyslovované anglicky jako F Sharp, /ef ʃɑɹp/, doslova to označuje notu fis) je multiparadigmatický programovací jazyk pro .NET spojující funkcionální přístup s imperativním objektově orientovaným přístupem. Syntaxe jazyka vychází z ML a OCaml a dále je ovlivněna jazyky Haskell, C# a LINQ. F# je plně podporovaným jazykem pro platformu .NET a je součástí Visual Studia[2] od verze 2010. V současné době se o vývoj jazyka stará Microsoft Research.
Paradigma | multiparadigmatický |
---|---|
Vznikl v | 2002 |
Autor | Microsoft Research, Don Syme |
Vývojář | Microsoft, The F# Software Foundation |
Poslední verze | 3.1.1[1] (24. 1. 2014) |
Typová kontrola | statické, silné, implicitní |
Ovlivněn jazyky | OCaml, C#, Python, Haskell, ML, LINQ, Scala, Erlang |
Ovlivnil jazyky | F*, LiveScript, C# |
OS | Multiplatformní (.NET, Mono) |
Licence | Apache license |
Web | Microsoft Research - F# |
Cíle jazyka a souhrn vlastností
F# byl vyvinutý jako varianta ML s mnoha konstrukcemi převzatými z OCaml. Na rozdíl od mnoha skriptovacích jazyků se rychlostí blíží k C# (především díky silné typovosti). Podporuje také řadu dynamických programovacích technik jako je například reflexe. F# umožňuje propojení s dalšími jazyky včetně snadné implementace DSL, bezproblémově spolupracuje se všemi jazyky .NET.
Microsoft Research[3] zmiňuje mezi hlavní výhody jazyka tyto:
- funkcionální jazyk se stručnou syntaxí a implicitním typováním
- možnost interaktivního skriptování (jako v Pythonu)
- kombinace typové bezpečnosti a implicitního typování (jako v ML)
- výkon na úrovni C#, nativní běh na .NET frameworku
- přístup ke všem knihovnám .NET
- integrovanost a plná podpora ve Visual Studiu
F# je silně typový jazyk který ovšem používá implicitní typování (datový typ proměnné se nemusí
specifikovat explicitně, překladač ho rozpozná podle přiřazované hodnoty). Jako jazyk pro .NET
podporuje F# všechny typy z .NET frameworku ale navíc přidává několik neměnných typů (změna
jejich hodnoty je možná pouze vytvořením nové kopie) svázaných se specifickými vlastnostmi
jazyka a používaných především pro úlohy funkcionálního programování. Těmito typy jsou: tuple,
record, discriminated union, list a function. V této souvislosti stojí za zmínku, že i typy .NET jsou
standardně v F# neměnné, opaku lze docílit použitím klíčového slova mutable
.
Důležitou vlastností jazyka je interaktivní skriptování, které umožňuje komponenta F# Interactive[4]. Syntaxe jazyka se pro skriptování v některých detailech liší, navíc je možné používat tzv. light syntaxi. Jde ale jen o drobné rozdíly v ukončování příkazů.
Syntaxe jazyka
Syntaxe jazyka jak už bylo řečeno vychází hlavně z ML a OCaml. Nejvýrazněji se na vzhledu kódu podepisují jazykové konstrukce implicitního typování a pattern matchingu.
V jazyce existují komentáře dvojího typu, řádkové uvozené //
a víceřádkové začínající znaky (*
a
končící *)
.
Proměnná (name v terminologii F#, protože se nejedná o proměnné v klasickém slova smyslu,
mohou totiž uchovávat i funkce) se deklaruje tak že jejímu názvu předchází klíčové slovo let
, které zároveň
znamená automatické určení typu proměnné (podobně jako var
v C# 3.0).
Hodnota se proměnné přiřadí při deklaraci pomocí operátoru =
, pokud je potřeba ji později
změnit (pouze v kontextu měnitelných proměnných označených klíčovým slovem mutable
), slouží
k tomu operátor <-
.
Pro spolupráci s .NET je důležitá konstrukce pro tvorbu objektů převzatá z C# - objekty se tvoří
pomocí klíčového slova new
. Pro „oznámení“ používání nějakého namespace se používá klíčové slovo
open
(ekvivalent using
v C#). K vlastnostem a metodám se přistupuje klasicky přes tečkovou notaci
a metody se volají s parametry v závorkách.
Mezi literály, mimo klasické čísla a řetězce, patří také některé konstrukce F#, jako tuple (viz dále).
Datový typ (třída) se deklaruje pomocí klíčového slova type
a to jak typy .NET tak i speciální typy F#,
konkrétněji viz níže nebo v[4].
Funkce se definují několika způsoby, buď pomocí klíčového slova fun
, nebo jako výraz kde na levé
straně jsou vedle let
a názvu také parametry a za rovnítkem následuje definice. Rekurzivní funkce
musejí být jako rekurzivní explicitně definovány pomocí klíčového slova rec
.
Důležitou konstrukcí F# je pattern matching, umožňuje porovnávat výraz s jinými a podle toho rozhodnout o dalším postupu výpočtu, přitom pojmenuje a tím získá přístup na složky dat. Nejčastější aplikací je rozložení typu tuple (viz níže) nebo řízení toku programu konstrukcí match proměnná with | když-výraz -> tak-výraz
kde část po |
se může libovolněkrát opakovat. Tato konstrukce může nahradit řadu ifů a switchů a zlepšit čitelnost kódu.
F# podporuje i základní cykly. Ty jsou vlastně funkce bez návratové hodnoty (v terminologii jazyka F# „výrazy typu unit
“).
Klasický cyklus for je uvozen klíčovým slovem for
, následuje řídící proměnná, rovnítko, počáteční hodnota, klíčové slovo to
nebo downto
(určující přičítání/odčítání jedničky), koncová hodnota, do
a tělo cyklu. Jinak řečeno: for řídící_prom = počáteční_hodnota [down]to konečná_hodnota do tělo_cyklu
.
Cyklus známý jako foreach (projde celé pole a do každé iterace nabídne jednu položku) se zapíše [for jedna_položka in procházené_pole do tělo_cyklu ]
.
Cyklus while má jednoduchou syntaxi while podminka do tělo_cyklu
.
Datové typy F#
Všechny typy F# jsou aliasy pro speciální generické datové typy .NET. Mimoto jsou v F# další aliasy na často používané třídy .NET, například obj
pro System.Object nebo ResizeArray<T>
pro System.Collections.Generic.List<T> (kvůli odlišení od F# typu List)[4].
Tuple
Tuple je nejjednodušší speciální datový typ v F#. Umožňuje „zabalit“ dvě nebo více nepojmenovaných hodnot každou libovolného typu. Množství i typ hodnot musejí být známy už při překladu. Je to v podstatě primitivní přepravka vhodná pro interní použití jež ani nemusí být explicitně definována jako typ (vytvoří se automaticky při použití). Každý tuple je odvozen od generické třídy Tuple<_,_>.
Příklad uložení jména se soundexovým kódem a jeho rozložení na hodnoty pomocí jednoduché konstrukce pattern matching:
let mrCarroll = ("Carroll", "C64")
let (name, soundex) = mrCarroll
Tentýž příklad v interaktivním režimu (s odpověďmi konzole):
> let mrCarroll = ("Carroll", "C64");;
val mrCarroll : string * string = ("Carroll", "C64")
> let (name, soundex) = mrCarroll;;
val soundex : string = "C64"
val name : string = "Carroll"
Record
Record (záznam) je rozšířeným typem tuple. Umožňuje pojmenovat jednotlivé datové složky a přistupovat k nim známou „tečkovou notací“, velmi se tak podobá typu struct v C#. Record už musí být definován jako typ. S .NET je record provázán tak že při jeho definici se vytvoří třída a jednotlivé datové složky má jako atributy.
Příklad vytvoření Recordu a odvození z něj druhého:
type ShopItem = { Name:string; Price:int }
let commonCar = { Name="car"; Price=1000 }
let luxuryCar = { commonCar with Price=5000 }
Discriminated union
Discriminated union je typ který umožňuje uložení hodnoty jednoho z různých nabídnutých typů. O konkrétním typu se rozhoduje podle volaného konstruktoru.
Příklad elegantní reprezentace binárního stromu pomocí Discriminated union. Zápis int *
BinaryTree * BinaryTree
vyjadřuje typ tuple s třemi položkami, první typu int a zbylé dvě typu
BinaryTree.
type BinaryTree =
| Fork of int * BinaryTree * BinaryTree
| Leaf of int
let heap = Fork (2,Fork (17,Fork (22,Leaf (49),Leaf (31)),Leaf (51)),Fork (29,Leaf(11),Leaf (25)))
List
List (seznam) je typický spojový seznam tvořený vždy položkou a odkazem na zbytek seznamu v zápise první_položka::zbytek_seznamu
(tzv. tail operátor známý např. z Lispu) nebo ve zkráceném zápise [ hodnota1;hodnota2;... ]
. Prázdný seznam se zapíše [].
F# obsahuje také speciální jazykovou konstrukci pro tvorbu typu array který ale je standardním typem .NET a není tedy neměnný. Lze ho vytvořit konstrukcí [| hodnota1;hodnota2;... |]
.
Příklad dvou možných zápisů tvorby seznamu List:
let list1 = 1::2::3::[]
let list2 = [1; 2; 3]
Function
V F# je každá funkce typem. Funkce jsou také neměnným typem, ale F# nabízí řadu možností jak upravovat jejich volání, například použitím curryingu nebo skládáním funkcí, funkce mohou být předávány jako parametry dalších funkcí, F# také podporuje lambda funkce.
Příklad definice jednoduché funkce pro sčítání a následně z ní odvozené funkce pro přičtení 10tky (jednoduchá ukázka techniky zvané currying) a nakonec volání které vyústí v hodnotu 20 uloženou v proměnné twenty:
let add = ( fun lhs rhs -> lhs + rhs )
let add10 = add 10
let twenty = add10 10
Funkci add lze také nadefinovat takto:
let add lhs rhs = lhs + rhs
Příklad funkce sumy všech hodnot v Listu. Řešeno pro funkcionální jazyky typickým způsobem (odpojení první položky, její zpracování, a rekurzivní volání na zbytek pole), využívá pattern matching a ilustruje deklaraci rekurzivní funkce klíčovým slovem rec
.
let list = [ 1 .. 10 ] (* seznam se všemi položkami od 1 do 10 *)
//pomocná funkce s akumulační proměnnou acc
let rec sumAcc acc list = match list with
| top::tail -> sumAcc (acc + top) tail
| _ -> acc
//výsledná funkce sumy
let sum list = sumAcc 0 list
let res = sum list (* val res : int = 55 *)
K předchozímu příkladu stojí za zmínku i vlastnost jazyka F# podobná se zásobníkovými jazyky, totiž že obě funkce by se mohly jmenovat stejně a program by fungoval správně, druhá by skryla první a sama by ji uvnitř bez problémů volala. Je to dáno způsobem, jakým v F# funguje obor hodnot proměnné[4].
Vlastnosti jazyka
F# používá pattern matching jednak aby převedl jména na hodnoty, ale také pro kontrolu, zda mají data požadovanou strukturu nebo hodnotu. Pattern matching může být použit se všemi standardními F# typy, nejčastěji s typy tuple a discriminated union. Jazyk F# také podporuje obecnější pattern matching pomocí tzv. active patterns, které umožňují kontrolu dat z různých pohledů na typ.
Protože je jazyk F# určen pro platformu .NET (musí vyhovovat požadavkům CLI) a protože jako jedno z paradigmat podporuje imperativní objektově orientované programování, obsahuje konstrukce pro tvorbu smyček for a while a .NET tříd i rozhraní (v terminologii F# společně nazývané object types – objektové typy) v podobném rozsahu jako ostatní jazyky .NET[4].
F# od verze 1.9.1 obsahuje tzv. sequence expressions (sekvenční výrazy)[5] zapisované jako seq{ ... }
, které obsahují sekvenční bloky různých konstrukcí. Na rozdíl od ostatních jazykových konstrukcí F# jsou „líně vykonávané“ (lazily evaluated, tj. vyhodnocují se až v momentě využití). Mohou být použity pro filtrování i pro zkrácení zápisu (mnoharozměrné) kolekce. Jsou základem pro podporu LINQ a důležité pro asynchronní volání (async{ ... }
).
F# je díky kombinaci vlastností funkcionálních a objektových jazyků vhodný na programování asynchronních operací a vícevláknových aplikací pro víceprocesorové systémy.
Jako jedno z paradigmat které F# do jisté míry přejímá je Language-oriented programming[4], usnadňuje tak tvorbu DSL, ovšem omezenou syntaxí jazyka (lze „pouze“ změnit význam příkazů F#). Nejjednodušší příklad je využití discriminated unions jako deklarativní jazyk na vyhodnocování aritmetických výrazů[4]. F# rozšiřuje schopnosti .NET System.Reflection a umožňuje tak meta-programming (meta programování – manipulování s kódem jako s daty).
Název jazyka
Název jazyka F# je, podobně jako v ostatních jazycích .NET (počínaje jazykem C#), odvozen z hudební notace, kde křížek označuje zvýšení noty o půl tónu a v tomto případě by označoval notu fis, tedy F zvýšené o půl tónu.
Křížek na počítačové klávesnici (#) a křížek v hudební nauce (♯) jsou dva odlišné znaky. Pro zápis názvu jazyka F Sharp se nepoužívá znak hudebního křížku z technických důvodů, protože tento se na standardní klávesnici nevyskytuje. Pro zjednodušení se tak používá klasický křížek.
Příklady
Výpis na standardní výstup:
printfn "Ahoj světe."
Jednoduchá rekurzivně definovaná funkce, která slouží k výpočtu faktoriálu:
let rec factorial n = match n with
| 0 -> 1
| _ -> n * factorial (n – 1)
Cyklus s předem známým počtem opakování (typu for), který vypíše celá čísla od 1 do 10:
for n = 1 to 10 do printfn "%d" n
Cyklus s předem neznámým počtem opakování (typu while), jenž vypíše celá čísla od 4 do 10:
let mutable n = 3
while n < 10 do
n <- (n+1)
printfn "%d" n
Cyklus s předem známým počtem opakování (typu foreach), který pole deseti celých čísel od 1 do 10 přetaví na pole deseti sudých čísel od 2 do 20:
let nums = [1..10]
[ for num in nums do yield num*2 ]
Jednoduchá aplikace .NET s grafickým uživatelským rozhraním:
(* otevření formulářové knihovny .NET *)
open System.Windows.Forms
(* vytvoření okna(Form) a nastavení vlastností *)
let form = new Form(Visible=true, TopMost=true, Text="Welcome to F#")
(* vytvoření nápisu(Label) s textem *)
let label =
let temp = new Label()
let x = 3 + (4 * 5)
(* nastavení hodnoty vlastnosti Text *)
temp.Text <- sprintf "x = %d" x
(* vrácení hodnoty (do proměnné label) *)
temp
(* přidání nápisu do okna *)
do form.Controls.Add(label)
(* spuštění aplikace *)
[<STAThread>]
do Application.Run(form)
Reference
- Archivovaná kopie. visualstudiogallery.msdn.microsoft.com [online]. [cit. 2014-10-05]. Dostupné v archivu pořízeném dne 2014-02-22.
- SOMASEGAR, Sam. F# - A Functional Programming Language [online]. [cit. 2009-12-02]. Dostupné online.
- Microsoft Research [online]. [cit. 2009-12-02]. Dostupné online.
- PETŘÍČEK, Tomáš. F# Language Overview [online]. [cit. 2009-12-01]. Dostupné online.
- SYME, Don. Some Details on F# Computation Expressions [online]. [cit. 2009-12-03]. Dostupné online.