Functions
Functies zijn de fundamentele eenheid van het uitvoeren van programma's in elke programmeertaal. Net als in andere talen heeft een F#-functie een naam, kan parameters bevatten en argumenten aannemen en een hoofdtekst hebben. F# ondersteunt ook functionele programmeerconstructies zoals het behandelen van functies als waarden, het gebruik van niet-benoemde functies in expressies, de samenstelling van functies om nieuwe functies te vormen, curriede functies en de impliciete definitie van functies door middel van de gedeeltelijke toepassing van functieargumenten.
U definieert functies met behulp van het let
trefwoord of, als de functie recursief is, de let rec
combinatie van trefwoorden.
Syntax
// Non-recursive function definition.
let [inline] function-name parameter-list [ : return-type ] = function-body
// Recursive function definition.
let rec function-name parameter-list = recursive-function-body
Opmerkingen
De functienaam is een id die de functie vertegenwoordigt. De parameterlijst bestaat uit opeenvolgende parameters die worden gescheiden door spaties. U kunt een expliciet type opgeven voor elke parameter, zoals beschreven in de sectie Parameters. Als u geen specifiek argumenttype opgeeft, probeert de compiler het type uit de hoofdtekst van de functie af te afleiden. De functietekst bestaat uit een expressie. De expressie waaruit de hoofdtekst van de functie bestaat, is doorgaans een samengestelde expressie die bestaat uit een aantal expressies die culmineren in een uiteindelijke expressie die de retourwaarde is. Het retourtype is een dubbele punt gevolgd door een type en is optioneel. Als u niet expliciet het type van de retourwaarde opgeeft, bepaalt de compiler het retourtype van de uiteindelijke expressie.
Een eenvoudige functiedefinitie lijkt op het volgende:
let f x = x + 1
In het vorige voorbeeld is f
de functienaam , het argument is x
, dat het type int
heeft , de hoofdtekst van de functie is x + 1
en de retourwaarde van het type int
.
Functies kunnen worden gemarkeerd inline
. Zie Inline Functions voor meer informatie overinline
.
Bereik
Op een ander bereiksniveau dan het modulebereik is het geen fout om een waarde of functienaam opnieuw te gebruiken. Als u een naam opnieuw gebruikt, schaduwt de naam die u later hebt gedeclareerd, de naam die u eerder hebt gedeclareerd. Op het hoogste niveau in een module moeten namen echter uniek zijn. Met de volgende code wordt bijvoorbeeld een fout gegenereerd wanneer deze wordt weergegeven in het modulebereik, maar niet wanneer deze binnen een functie wordt weergegeven:
let list1 = [ 1; 2; 3]
// Error: duplicate definition.
let list1 = []
let function1 () =
let list1 = [1; 2; 3]
let list1 = []
list1
Maar de volgende code is acceptabel op elk niveau van het bereik:
let list1 = [ 1; 2; 3]
let sumPlus x =
// OK: inner list1 hides the outer list1.
let list1 = [1; 5; 10]
x + List.sum list1
Parameters
Namen van parameters worden weergegeven na de functienaam. U kunt een type voor een parameter opgeven, zoals wordt weergegeven in het volgende voorbeeld:
let f (x : int) = x + 1
Als u een type opgeeft, volgt deze de naam van de parameter en wordt deze gescheiden van de naam door een dubbele punt. Als u het type voor de parameter weglaat, wordt het parametertype afgeleid door de compiler. In de volgende functiedefinitie wordt het argument x
bijvoorbeeld afgeleid van het type int
omdat 1 van het type int
is.
let f x = x + 1
De compiler probeert de functie echter zo algemeen mogelijk te maken. Noteer bijvoorbeeld de volgende code:
let f x = (x, x)
De functie maakt een tuple van één argument van elk type. Omdat het type niet is opgegeven, kan de functie worden gebruikt met elk argumenttype. Zie Automatische generalisatie voor meer informatie.
Functieteksten
Een functietekst kan definities van lokale variabelen en functies bevatten. Dergelijke variabelen en functies vallen binnen het bereik van de hoofdtekst van de huidige functie, maar niet buiten de functie. U moet inspringing gebruiken om aan te geven dat een definitie zich in een functietekst bevindt, zoals wordt weergegeven in het volgende voorbeeld:
let cylinderVolume radius length =
// Define a local value pi.
let pi = 3.14159
length * pi * radius * radius
Zie Richtlijnen voor codeopmaak en uitgebreide syntaxis voor meer informatie.
Retourwaarden
De compiler gebruikt de uiteindelijke expressie in een functietekst om de retourwaarde en het type te bepalen. De compiler kan het type van de uiteindelijke expressie afleiden uit eerdere expressies. In de functie cylinderVolume
, weergegeven in de vorige sectie, wordt het type pi
bepaald van het type van de letterlijke naam 3.14159
float
. De compiler gebruikt het type om pi
het type expressie length * pi * radius * radius
te float
bepalen. Daarom is float
het algemene retourtype van de functie.
Als u het retourtype expliciet wilt opgeven, schrijft u de code als volgt:
let cylinderVolume radius length : float =
// Define a local value pi.
let pi = 3.14159
length * pi * radius * radius
Zoals de code hierboven is geschreven, past de compiler float toe op de hele functie; Als u deze ook wilt toepassen op de parametertypen, gebruikt u de volgende code:
let cylinderVolume (radius : float) (length : float) : float
Een functie aanroepen
U roept functies aan door de functienaam op te geven, gevolgd door een spatie en vervolgens argumenten gescheiden door spaties. Als u bijvoorbeeld het function cylinderVolume wilt aanroepen en het resultaat wilt toewijzen aan de waarde vol, schrijft u de volgende code:
let vol = cylinderVolume 2.0 3.0
Gedeeltelijke toepassing van argumenten
Als u minder dan het opgegeven aantal argumenten opgeeft, maakt u een nieuwe functie die de resterende argumenten verwacht. Deze methode voor het afhandelen van argumenten wordt currying genoemd en is een kenmerk van functionele programmeertalen zoals F#. Stel dat u met twee grootten pijp werkt: de ene heeft een straal van 2.0 en de andere een straal van 3,0. U kunt als volgt functies maken die het volume van de pijp bepalen:
let smallPipeRadius = 2.0
let bigPipeRadius = 3.0
// These define functions that take the length as a remaining
// argument:
let smallPipeVolume = cylinderVolume smallPipeRadius
let bigPipeVolume = cylinderVolume bigPipeRadius
Vervolgens zou u het laatste argument indien nodig opgeven voor verschillende lengten van de pijp van de twee verschillende grootten:
let length1 = 30.0
let length2 = 40.0
let smallPipeVol1 = smallPipeVolume length1
let smallPipeVol2 = smallPipeVolume length2
let bigPipeVol1 = bigPipeVolume length1
let bigPipeVol2 = bigPipeVolume length2
Recursieve functies
Recursieve functies zijn functies die zichzelf aanroepen. Hiervoor moet u het trefwoord rec opgeven volgens het trefwoord toestaan . Roep de recursieve functie aan vanuit de hoofdtekst van de functie, net zoals u een functieoproep zou aanroepen. Met de volgende recursieve functie wordt het ne Fibonacci-getal berekend. De Fibonacci-getalreeks is bekend sinds de oudheid en is een reeks waarin elk opeenvolgend getal de som is van de vorige twee getallen in de reeks.
let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)
Sommige recursieve functies kunnen de programmastack overlopen of efficiënt uitvoeren als u ze niet met zorg schrijft en bewust bent van speciale technieken, zoals het gebruik van staartrecursie, accumulators en vervolgen.
Functiewaarden
In F# worden alle functies beschouwd als waarden; In feite worden ze functiewaarden genoemd. Omdat functies waarden zijn, kunnen ze worden gebruikt als argumenten voor andere functies of in andere contexten waarin waarden worden gebruikt. Hieronder volgt een voorbeeld van een functie die een functiewaarde als argument gebruikt:
let apply1 (transform : int -> int ) y = transform y
U geeft het type van een functiewaarde op met behulp van het ->
token. Aan de linkerkant van dit token is het type van het argument en aan de rechterkant is de retourwaarde. In het vorige voorbeeld apply1
is een functie die een functie transform
als argument gebruikt, waarbij transform
een functie een geheel getal gebruikt en een ander geheel getal retourneert. De volgende code laat zien hoe u apply1
gebruikt:
let increment x = x + 1
let result1 = apply1 increment 100
De waarde van result
101 is nadat de vorige code is uitgevoerd.
Meerdere argumenten worden gescheiden door opeenvolgende ->
tokens, zoals wordt weergegeven in het volgende voorbeeld:
let apply2 ( f: int -> int -> int) x y = f x y
let mul x y = x * y
let result2 = apply2 mul 10 20
Het resultaat is 200.
Lambda-expressies
Een lambda-expressie is een niet-benoemde functie. In de vorige voorbeelden kunt u lambda-expressies als volgt gebruiken in plaats van benoemde functies te definiëren:
let result3 = apply1 (fun x -> x + 1) 100
let result4 = apply2 (fun x y -> x * y ) 10 20
U definieert lambda-expressies met behulp van het fun
trefwoord. Een lambda-expressie lijkt op een functiedefinitie, behalve dat in plaats van het =
token het ->
token wordt gebruikt om de lijst met argumenten te scheiden van de hoofdtekst van de functie. Net als in een normale functiedefinitie kunnen de argumenttypen expliciet worden afgeleid of opgegeven en wordt het retourtype van de lambda-expressie afgeleid van het type van de laatste expressie in de hoofdtekst. Zie Lambda-expressies: Het fun
trefwoord voor meer informatie.
Pipelines
De pijpoperator |>
wordt uitgebreid gebruikt bij het verwerken van gegevens in F#. Met deze operator kunt u 'pijplijnen' van functies op een flexibele manier tot stand brengen. Pipelining maakt het mogelijk om functie-aanroepen samen te koppelen als opeenvolgende bewerkingen:
let result = 100 |> function1 |> function2
In het volgende voorbeeld wordt uitgelegd hoe u deze operators kunt gebruiken om een eenvoudige functionele pijplijn te bouwen:
/// Square the odd values of the input and add one, using F# pipe operators.
let squareAndAddOdd values =
values
|> List.filter (fun x -> x % 2 <> 0)
|> List.map (fun x -> x * x + 1)
let numbers = [ 1; 2; 3; 4; 5 ]
let result = squareAndAddOdd numbers
Het resultaat is [2; 10; 26]
. In het vorige voorbeeld worden lijstverwerkingsfuncties gebruikt, waarmee wordt gedemonstreerd hoe functies kunnen worden gebruikt om gegevens te verwerken bij het bouwen van pijplijnen. De pijplijnoperator zelf wordt als volgt gedefinieerd in de F#-kernbibliotheek:
let (|>) x f = f x
Functiesamenstelling
Functies in F# kunnen worden samengesteld uit andere functies. De samenstelling van twee functies , functie1 en functie2 , is een andere functie die de toepassing van functie1 vertegenwoordigt, gevolgd door de toepassing van functie2:
let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100
Het resultaat is 202.
De samenstellingsoperator >>
neemt twee functies en retourneert een functie. De pijplijnoperator |>
neemt daarentegen een waarde en een functie en retourneert een waarde. In het volgende codevoorbeeld ziet u het verschil tussen de pijplijn- en samenstellingsoperatoren door de verschillen in de functiehandtekeningen en het gebruik weer te geven.
// Function composition and pipeline operators compared.
let addOne x = x + 1
let timesTwo x = 2 * x
// Composition operator
// ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3
let Compose2 = addOne >> timesTwo
// Backward composition operator
// ( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
let Compose1 = addOne << timesTwo
// Result is 5
let result1 = Compose1 2
// Result is 6
let result2 = Compose2 2
// Pipelining
// Pipeline operator
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo
// Backward pipeline operator
// ( <| ) : ('T -> 'U) -> 'T -> 'U
let Pipeline1 x = addOne <| timesTwo x
// Result is 5
let result3 = Pipeline1 2
// Result is 6
let result4 = Pipeline2 2
Overbelastingsfuncties
U kunt methoden van een type overbelasten, maar geen functies. Zie Methoden voor meer informatie.