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.

  1. Wpisz aliasy
  2. Dyskryminowane typy związków
  3. Typy rekordów
  4. Typy interfejsów
  5. Rodzaje klas
  6. 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


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow