Zoeken…
Inleiding tot typen
Typen kunnen verschillende soorten dingen vertegenwoordigen. Het kan een enkele data, een set data of een functie zijn.
In F # kunnen we de typen in twee categorieën groeperen:
F # types:
// Functions let a = fun c -> c // Tuples let b = (0, "Foo") // Unit type let c = ignore // Records type r = { Name : string; Age : int } let d = { Name = "Foo"; Age = 10 } // Discriminated Unions type du = | Foo | Bar let e = Bar // List and seq let f = [ 0..10 ] let g = seq { 0..10 } // Aliases type MyAlias = string
.NET-typen
- Ingebouwd type (int, bool, string, ...)
- Klassen, structuren en interfaces
- afgevaardigden
- arrays
Type afkortingen
Met typeafkortingen kunt u aliassen maken op bestaande typen om ze een betekenisvollere betekenis te geven.
// Name is an alias for a string
type Name = string
// PhoneNumber is an alias for a string
type PhoneNumber = string
Dan kunt u de alias net als elk ander type gebruiken:
// Create a record type with the alias
type Contact = {
Name : Name
Phone : PhoneNumber }
// Create a record instance
// We can assign a string since Name and PhoneNumber are just aliases on string type
let c = {
Name = "Foo"
Phone = "00 000 000" }
printfn "%A" c
// Output
// {Name = "Foo";
// Phone = "00 000 000";}
Wees voorzichtig, aliassen controleren niet op typeconsistentie. Dit betekent dat twee aliassen die op hetzelfde type zijn gericht, aan elkaar kunnen worden toegewezen:
let c = {
Name = "Foo"
Phone = "00 000 000" }
let d = {
Name = c.Phone
Phone = c.Name }
printfn "%A" d
// Output
// {Name = "00 000 000";
// Phone = "Foo";}
Typen worden gemaakt in F # met behulp van het trefwoord type
F#
gebruikt het sleutelwoord type
om verschillende soorten typen te maken.
- Typ aliassen
- Discriminatie van unietypen
- Recordtypen
- Interface types
- Klasse typen
- Struct types
Voorbeelden met gelijkwaardige C#
-code waar mogelijk:
// Equivalent C#:
// using IntAliasType = System.Int32;
type IntAliasType = int // As in C# this doesn't create a new type, merely an alias
type DiscriminatedUnionType =
| FirstCase
| SecondCase of int*string
member x.SomeProperty = // We can add members to DU:s
match x with
| FirstCase -> 0
| SecondCase (i, _) -> i
type RecordType =
{
Id : int
Name : string
}
static member New id name : RecordType = // We can add members to records
{ Id = id; Name = name } // { ... } syntax used to create records
// Equivalent C#:
// interface InterfaceType
// {
// int Id { get; }
// string Name { get; }
// int Increment (int i);
// }
type InterfaceType =
interface // In order to create an interface type, can also use [<Interface>] attribute
abstract member Id : int
abstract member Name : string
abstract member Increment : int -> int
end
// Equivalent C#:
// class ClassType : InterfaceType
// {
// static int increment (int i)
// {
// return i + 1;
// }
//
// public ClassType (int id, string name)
// {
// Id = id ;
// Name = name ;
// }
//
// public int Id { get; private set; }
// public string Name { get; private set; }
// public int Increment (int i)
// {
// return increment (i);
// }
// }
type ClassType (id : int, name : string) = // a class type requires a primary constructor
let increment i = i + 1 // Private helper functions
interface InterfaceType with // Implements InterfaceType
member x.Id = id
member x.Name = name
member x.Increment i = increment i
// Equivalent C#:
// class SubClassType : ClassType
// {
// public SubClassType (int id, string name) : base(id, name)
// {
// }
// }
type SubClassType (id : int, name : string) =
inherit ClassType (id, name) // Inherits ClassType
// Equivalent C#:
// struct StructType
// {
// public StructType (int id)
// {
// Id = id;
// }
//
// public int Id { get; private set; }
// }
type StructType (id : int) =
struct // In order create a struct type, can also use [<Struct>] attribute
member x.Id = id
end
Type Inferentie
Erkenning
Dit voorbeeld is overgenomen uit dit artikel over type-inferentie
Wat is type inferentie?
Type Inferentie is het mechanisme waarmee de compiler kan afleiden welke typen worden gebruikt en waar. Dit mechanisme is gebaseerd op een algoritme dat vaak "Hindley-Milner" of "HM" wordt genoemd. Zie hieronder enkele regels voor het bepalen van de soorten eenvoudige en functiewaarden:
- Kijk naar de letterlijke dingen
- Kijk naar de functies en andere waarden waar iets mee interageert
- Bekijk eventuele expliciete typebeperkingen
- Als er nergens beperkingen zijn, generaliseer dan automatisch naar generieke typen
Kijk naar de letterlijke dingen
De compiler kan typen afleiden door naar de letterlijke waarden te kijken. Als het letterlijke een int is en u voegt "x" eraan toe, dan moet "x" ook een int zijn. Maar als het letterlijke een float is en u voegt "x" eraan toe, dan moet "x" ook een float zijn.
Hier zijn enkele voorbeelden:
let inferInt x = x + 1
let inferFloat x = x + 1.0
let inferDecimal x = x + 1m // m suffix means decimal
let inferSByte x = x + 1y // y suffix means signed byte
let inferChar x = x + 'a' // a char
let inferString x = x + "my string"
Bekijk de functies en andere waarden waarmee het samenwerkt
Als er nergens letterlijk is, probeert de compiler de typen uit te werken door de functies en andere waarden te analyseren waarmee ze communiceren.
let inferInt x = x + 1
let inferIndirectInt x = inferInt x //deduce that x is an int
let inferFloat x = x + 1.0
let inferIndirectFloat x = inferFloat x //deduce that x is a float
let x = 1
let y = x //deduce that y is also an int
Bekijk expliciete typebeperkingen of annotaties
Als er expliciete typebeperkingen of annotaties zijn opgegeven, gebruikt de compiler deze.
let inferInt2 (x:int) = x // Take int as parameter
let inferIndirectInt2 x = inferInt2 x // Deduce from previous that x is int
let inferFloat2 (x:float) = x // Take float as parameter
let inferIndirectFloat2 x = inferFloat2 x // Deduce from previous that x is float
Automatische generalisatie
Als er na dit alles geen beperkingen worden gevonden, maakt de compiler alleen de typen generiek.
let inferGeneric x = x
let inferIndirectGeneric x = inferGeneric x
let inferIndirectGenericAgain x = (inferIndirectGeneric x).ToString()
Dingen die mis kunnen gaan met type gevolgtrekking
De type inferentie is helaas niet perfect. Soms heeft de compiler gewoon geen idee wat hij moet doen. Nogmaals, als je begrijpt wat er gebeurt, zul je echt kalm blijven in plaats van de compiler te willen doden. Hier zijn enkele van de belangrijkste redenen voor typefouten:
- Verklaringen buiten gebruik
- Niet genoeg informatie
- Overbelaste methoden
Verklaringen buiten gebruik
Een basisregel is dat u functies moet declareren voordat ze worden gebruikt.
Deze code mislukt:
let square2 x = square x // fails: square not defined
let square x = x * x
Maar dit is goed:
let square x = x * x
let square2 x = square x // square already defined earlier
Recursieve of gelijktijdige verklaringen
Een variant van het probleem 'buiten werking' treedt op bij recursieve functies of definities die naar elkaar moeten verwijzen. In dit geval helpt geen enkele herschikking - we moeten extra trefwoorden gebruiken om de compiler te helpen.
Wanneer een functie wordt gecompileerd, is de functie-ID niet beschikbaar voor het lichaam. Dus als u een eenvoudige recursieve functie definieert, krijgt u een compilerfout. De oplossing is om het trefwoord "rec" toe te voegen als onderdeel van de functiedefinitie. Bijvoorbeeld:
// the compiler does not know what "fib" means
let fib n =
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
// error FS0039: The value or constructor 'fib' is not defined
Hier is de vaste versie met "rec fib" toegevoegd om aan te geven dat het recursief is:
let rec fib n = // LET REC rather than LET
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
Niet genoeg informatie
Soms heeft de compiler gewoon niet genoeg informatie om een type te bepalen. In het volgende voorbeeld weet de compiler niet op welk type de lengtemethode zou moeten werken. Maar het kan het ook niet generiek maken, dus klaagt het.
let stringLength s = s.Length
// error FS0072: Lookup on object of indeterminate type
// based on information prior to this program point.
// A type annotation may be needed ...
Dit soort fouten kan worden opgelost met expliciete annotaties.
let stringLength (s:string) = s.Length
Overbelaste methoden
Wanneer u een externe klasse of methode in .NET aanroept, krijgt u vaak fouten als gevolg van overbelasting.
In veel gevallen, zoals het onderstaande concat-voorbeeld, moet u de parameters van de externe functie expliciet annoteren, zodat de compiler weet welke overbelaste methode moet worden aangeroepen.
let concat x = System.String.Concat(x) //fails
let concat (x:string) = System.String.Concat(x) //works
let concat x = System.String.Concat(x:string) //works
Soms hebben de overbelaste methoden verschillende argumentnamen, in welk geval u de compiler ook een idee kunt geven door de argumenten een naam te geven. Hier is een voorbeeld voor de StreamReader-constructor.
let makeStreamReader x = new System.IO.StreamReader(x) //fails
let makeStreamReader x = new System.IO.StreamReader(path=x) //works