Suche…
Einführung in Typen
Typen können verschiedene Arten von Dingen darstellen. Es können einzelne Daten, ein Datensatz oder eine Funktion sein.
In F # können wir die Typen in zwei Kategorien einteilen:
F # Typen:
// 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
- Eingebauter Typ (int, bool, string, ...)
- Klassen, Strukturen und Schnittstellen
- Delegierte
- Arrays
Typ Abkürzungen
Mit Typabkürzungen können Sie Aliase für vorhandene Typen erstellen, um ihnen einen sinnvollen Sinn zu geben.
// Name is an alias for a string
type Name = string
// PhoneNumber is an alias for a string
type PhoneNumber = string
Dann können Sie den Alias wie jeden anderen Typ verwenden:
// 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";}
Seien Sie vorsichtig, Aliase prüfen nicht auf Typkonsistenz. Das bedeutet, dass zwei Aliase, die auf denselben Typ abzielen, einander zugewiesen werden können:
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 werden in F # mit dem Schlüsselwort type erstellt
F#
verwendet das Schlüsselwort type
, um verschiedene Arten von Typen zu erstellen.
- Geben Sie Aliase ein
- Diskriminierte Gewerkschaftstypen
- Aufnahmetypen
- Schnittstellentypen
- Klassenarten
- Strukturtypen
Beispiele mit gleichwertigem C#
-Code, soweit möglich:
// 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
Typ Inferenz
Wissen
Dieses Beispiel wurde aus diesem Artikel zur Typeninferenz angepasst
Was ist Typ Inferenz?
Type Inference ist der Mechanismus, mit dem der Compiler ableiten kann, welche Typen wo verwendet werden. Dieser Mechanismus basiert auf einem Algorithmus, der häufig als "Hindley-Milner" oder "HM" bezeichnet wird. Nachfolgend einige Regeln zum Ermitteln der Typen von einfachen Werten und Funktionswerten:
- Schau dir die Literale an
- Schauen Sie sich die Funktionen und andere Werte an, mit denen etwas interagiert
- Sehen Sie sich explizite Typeinschränkungen an
- Wenn es nirgends irgendwelche Einschränkungen gibt, generalisieren Sie automatisch auf generische Typen
Schau dir die Literale an
Der Compiler kann Typen anhand der Literale ableiten. Wenn das Literal ein int ist und Sie "x" hinzufügen, muss "x" ebenfalls ein int sein. Wenn das Literal jedoch ein Float ist und Sie "x" hinzufügen, muss "x" ebenfalls ein Float sein.
Hier sind einige Beispiele:
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"
Schauen Sie sich die Funktionen und andere Werte an, mit denen sie interagieren
Wenn nirgendwo Literale vorhanden sind, versucht der Compiler, die Typen zu ermitteln, indem er die Funktionen und andere Werte analysiert, mit denen sie interagieren.
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
Sehen Sie sich explizite Typeinschränkungen oder Anmerkungen an
Wenn explizite Typeinschränkungen oder Anmerkungen angegeben wurden, verwendet der Compiler diese.
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 Generalisierung
Wenn nach all dem keine Einschränkungen gefunden werden, macht der Compiler die Typen nur zu generisch.
let inferGeneric x = x
let inferIndirectGeneric x = inferGeneric x
let inferIndirectGenericAgain x = (inferIndirectGeneric x).ToString()
Dinge, die durch Typeninferenz schief gehen können
Der Typ Inferenz ist leider nicht perfekt. Manchmal hat der Compiler keine Ahnung, was er tun soll. Wenn Sie wissen, was passiert, können Sie wirklich ruhig bleiben, anstatt den Compiler töten zu wollen. Hier sind einige der Hauptgründe für Typfehler:
- Erklärungen außer Betrieb
- Nicht genug Information
- Überlastete Methoden
Erklärungen außer Betrieb
Eine grundlegende Regel ist, dass Sie Funktionen deklarieren müssen, bevor sie verwendet werden.
Dieser Code schlägt fehl:
let square2 x = square x // fails: square not defined
let square x = x * x
Das ist aber ok:
let square x = x * x
let square2 x = square x // square already defined earlier
Rekursive oder gleichzeitige Deklarationen
Eine Variante des Problems "Außer Betrieb" tritt bei rekursiven Funktionen oder Definitionen auf, die sich aufeinander beziehen müssen. In diesem Fall hilft keine Neuordnung. Wir müssen zusätzliche Schlüsselwörter verwenden, um den Compiler zu unterstützen.
Wenn eine Funktion kompiliert wird, ist der Funktionsbezeichner für den Körper nicht verfügbar. Wenn Sie also eine einfache rekursive Funktion definieren, wird ein Compiler-Fehler angezeigt. Das Update besteht darin, das Schlüsselwort "rec" als Teil der Funktionsdefinition hinzuzufügen. Zum Beispiel:
// 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 ist die feste Version mit "rec fib" hinzugefügt, um anzuzeigen, dass sie rekursiv ist:
let rec fib n = // LET REC rather than LET
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
Nicht genug Information
Manchmal hat der Compiler nicht genügend Informationen, um einen Typ zu bestimmen. Im folgenden Beispiel weiß der Compiler nicht, mit welchem Typ die Length-Methode arbeiten soll. Aber es kann es auch nicht generisch machen, klagt es.
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 ...
Diese Fehler können durch explizite Anmerkungen behoben werden.
let stringLength (s:string) = s.Length
Überlastete Methoden
Wenn Sie eine externe Klasse oder Methode in .NET aufrufen, erhalten Sie häufig Fehler aufgrund von Überladung.
In vielen Fällen, z. B. im folgenden concat-Beispiel, müssen Sie die Parameter der externen Funktion explizit kommentieren, damit der Compiler weiß, welche überladene Methode aufgerufen wird.
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
Manchmal haben die überladenen Methoden andere Argumentnamen. In diesem Fall können Sie dem Compiler auch einen Hinweis geben, indem Sie die Argumente benennen. Hier ist ein Beispiel für den StreamReader-Konstruktor.
let makeStreamReader x = new System.IO.StreamReader(x) //fails
let makeStreamReader x = new System.IO.StreamReader(path=x) //works