Szukaj…
Wprowadzenie do typów
Typy mogą reprezentować różnego rodzaju rzeczy. Może to być pojedynczy dane, zestaw danych lub funkcja.
W F # możemy pogrupować typy na dwie kategorie:
Typy F #:
// 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
Typy .NET
- Wbudowany typ (int, bool, string, ...)
- Klasy, konstrukcje i interfejsy
- Delegaci
- Tablice
Skróty typu
Skróty typów pozwalają tworzyć aliasy dla istniejących typów, aby nadać im bardziej znaczący zmysł.
// Name is an alias for a string
type Name = string
// PhoneNumber is an alias for a string
type PhoneNumber = string
Następnie możesz użyć aliasu tak jak każdego innego typu:
// 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";}
Uważaj, aliasy nie sprawdzają spójności typu. Oznacza to, że można przypisać do siebie dwa aliasy kierowane na ten sam typ:
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";}
Typy są tworzone w F # przy użyciu słowa kluczowego type
F#
używa słowa kluczowego type
do tworzenia różnego rodzaju typów.
- Wpisz aliasy
- Dyskryminowane typy związków
- Typy rekordów
- Typy interfejsów
- Rodzaje klas
- Rodzaje konstrukcji
Przykłady z równoważnym kodem C#
jeśli to możliwe:
// 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
Wnioskowanie typu
Potwierdzenie
Ten przykład został zaadaptowany z tego artykułu na temat wnioskowania o typie
Co to jest wnioskowanie typu?
Wnioskowanie typu jest mechanizmem, który pozwala kompilatorowi wydedukować, jakie typy są używane i gdzie. Mechanizm ten opiera się na algorytmie często nazywanym „Hindley-Milner” lub „HM”. Zobacz poniżej niektóre zasady określania typów wartości prostych i funkcji:
- Spójrz na literały
- Spójrz na funkcje i inne wartości, z którymi coś wchodzi w interakcje
- Spójrz na wszelkie wyraźne ograniczenia typu
- Jeśli nigdzie nie ma żadnych ograniczeń, automatycznie uogólnij na typy ogólne
Spójrz na literały
Kompilator może wydedukować typy, patrząc na literały. Jeśli literał jest liczbą całkowitą i dodajesz do niego „x”, to „x” również musi być liczbą całkowitą. Ale jeśli literał jest liczbą zmiennoprzecinkową i dodajesz do niego „x”, to „x” również musi być zmiennoprzecinkowe.
Oto kilka przykładów:
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"
Spójrz na funkcje i inne wartości, z którymi wchodzi w interakcje
Jeśli nigdzie nie ma literałów, kompilator próbuje wypracować typy, analizując funkcje i inne wartości, z którymi wchodzą w interakcje.
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
Spójrz na wszelkie wyraźne ograniczenia typu lub adnotacje
Jeśli określono jakieś wyraźne ograniczenia typu lub adnotacje, kompilator ich użyje.
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
Automatyczne uogólnienie
Jeśli po tym wszystkim nie znaleziono żadnych ograniczeń, kompilator po prostu czyni typy rodzajowymi.
let inferGeneric x = x
let inferIndirectGeneric x = inferGeneric x
let inferIndirectGenericAgain x = (inferIndirectGeneric x).ToString()
Rzeczy, które mogą pójść nie tak z wnioskowaniem typu
Wnioskowanie typu nie jest niestety idealne. Czasami kompilator po prostu nie ma pojęcia, co zrobić. Znów zrozumienie tego, co się dzieje, naprawdę pomoże ci zachować spokój, zamiast chcieć zabić kompilator. Oto niektóre z głównych przyczyn błędów typu:
- Deklaracje nieczynne
- Za mało informacji
- Przeciążone metody
Deklaracje nieczynne
Podstawową zasadą jest to, że musisz zadeklarować funkcje przed ich użyciem.
Ten kod nie działa:
let square2 x = square x // fails: square not defined
let square x = x * x
Ale to jest w porządku:
let square x = x * x
let square2 x = square x // square already defined earlier
Deklaracje rekurencyjne lub jednoczesne
Wariant problemu „poza kolejnością” występuje w przypadku funkcji rekurencyjnych lub definicji, które muszą się ze sobą odnosić. W tym przypadku nie pomoże żadna zmiana kolejności - musimy użyć dodatkowych słów kluczowych, aby pomóc kompilatorowi.
Podczas kompilowania funkcji identyfikator funkcji nie jest dostępny dla treści. Jeśli więc zdefiniujesz prostą funkcję rekurencyjną, pojawi się błąd kompilatora. Rozwiązaniem jest dodanie słowa kluczowego „rec” jako części definicji funkcji. Na przykład:
// 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
Oto poprawiona wersja z dodanym „rec fib”, aby wskazać, że jest rekurencyjna:
let rec fib n = // LET REC rather than LET
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
Za mało informacji
Czasami kompilator po prostu nie ma wystarczających informacji, aby określić typ. W poniższym przykładzie kompilator nie wie, na jakim typie powinna działać metoda Length. Ale nie może też sprawić, że będzie ogólny, więc narzeka.
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 ...
Tego rodzaju błędy można naprawić za pomocą wyraźnych adnotacji.
let stringLength (s:string) = s.Length
Przeciążone metody
Podczas wywoływania zewnętrznej klasy lub metody w .NET często pojawiają się błędy z powodu przeciążenia.
W wielu przypadkach, takich jak poniższy przykład konkat, będziesz musiał jawnie opisać parametry funkcji zewnętrznej, aby kompilator wiedział, którą przeciążoną metodę wywołać.
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
Czasami przeciążone metody mają różne nazwy argumentów, w takim przypadku można również dać kompilatorowi wskazówkę, nazywając argumenty. Oto przykład konstruktora StreamReader.
let makeStreamReader x = new System.IO.StreamReader(x) //fails
let makeStreamReader x = new System.IO.StreamReader(path=x) //works