Поиск…


Введение в типы

Типы могут представлять разные вещи. Это могут быть отдельные данные, набор данных или функция.

В F # мы можем сгруппировать типы по двум категориям:

  • Типы 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
    
  • Типы .NET

    • Встроенный тип (int, bool, string, ...)
    • Классы, структуры и интерфейсы
    • Делегаты
    • Массивы

Аббревиатуры типов

Аббревиатуры типов позволяют создавать псевдонимы на существующих типах, чтобы придать им более значимые ощущения.

// Name is an alias for a string
type Name = string

// PhoneNumber is an alias for a string
type PhoneNumber = string

Затем вы можете использовать псевдоним так же, как и любой другой тип:

// 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";}

Будьте осторожны, алиасы не проверяют соответствие типов. Это означает, что два псевдонима, которые нацелены на один и тот же тип, могут быть назначены друг другу:

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";}

Типы создаются в F #, используя ключевое слово типа

F# использует ключевое слово type для создания различных типов типов.

  1. Тип псевдонимов
  2. Типы дискриминационного союза
  3. Типы записей
  4. Типы интерфейсов
  5. Типы классов
  6. Типы типов

Примеры с эквивалентным кодом C# где это возможно:

// 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 Inference - это механизм, позволяющий компилятору определить, какие типы используются и где. Этот механизм основан на алгоритме, который часто называют «Hindley-Milner» или «HM». Ниже приведены некоторые правила определения типов простых и функциональных значений:

  • Посмотрите на литералы
  • Посмотрите на функции и другие ценности, с которыми что-то взаимодействует
  • Посмотрите на любые явные ограничения типа
  • Если в любом месте нет ограничений, автоматически обобщайте общие типы

Посмотрите на литералы

Компилятор может выводить типы, просматривая литералы. Если литерал является int, и вы добавляете к нему «x», тогда «x» также должен быть int. Но если литерал является float, и вы добавляете к нему «x», тогда «x» также должен быть float.

Вот некоторые примеры:

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"

Посмотрите на функции и другие значения, с которыми он взаимодействует

Если литералов нет, компилятор пытается выработать типы, анализируя функции и другие значения, с которыми они взаимодействуют.

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

Посмотрите на какие-либо явные ограничения типа или аннотации

Если есть какие-либо явные ограничения типа или аннотации, то компилятор будет их использовать.

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

Автоматическое обобщение

Если после всего этого не обнаружено ограничений, компилятор просто делает типы обобщенными.

let inferGeneric x = x 
let inferIndirectGeneric x = inferGeneric x 
let inferIndirectGenericAgain x = (inferIndirectGeneric x).ToString() 

Вещи, которые могут пойти не так с типом вывода

Увы, тип вывода не увенчан успехом. Иногда компилятор просто не знает, что делать. Опять же, понимание того, что происходит, действительно поможет вам оставаться спокойным, а не желать убить компилятора. Вот некоторые из основных причин ошибок типа:

  • Объявления не в порядке
  • Не хватает информации
  • Перегруженные методы

Объявления не в порядке

Основное правило заключается в том, что вы должны объявлять функции до их использования.

Этот код не работает:

let square2 x = square x   // fails: square not defined
let square x = x * x

Но это нормально:

let square x = x * x       
let square2 x = square x   // square already defined earlier

Рекурсивные или одновременные объявления

Вариант проблемы «не в порядке» возникает с рекурсивными функциями или определениями, которые должны ссылаться друг на друга. Никакое количество переупорядочения не поможет в этом случае - нам нужно использовать дополнительные ключевые слова, чтобы помочь компилятору.

Когда функция компилируется, идентификатор функции недоступен для тела. Поэтому, если вы определите простую рекурсивную функцию, вы получите ошибку компилятора. Исправление состоит в том, чтобы добавить ключевое слово «rec» как часть определения функции. Например:

// 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

Вот фиксированная версия с добавлением «rec fib», чтобы указать, что она рекурсивна:

let rec fib n =              // LET REC rather than LET
   if n <= 2 then 1
   else fib (n - 1) + fib (n - 2)

Не хватает информации

Иногда для определения типа компилятор просто не располагает достаточной информацией. В следующем примере компилятор не знает, на какой тип должен работать метод Length. Но он не может сделать его общим, поэтому он жалуется.

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 ...

Эти ошибки могут быть исправлены с помощью явных аннотаций.

let stringLength (s:string) = s.Length

Перегруженные методы

При вызове внешнего класса или метода в .NET вы часто будете получать ошибки из-за перегрузки.

Во многих случаях, таких как пример concat ниже, вам придется явно аннотировать параметры внешней функции, чтобы компилятор знал, какой перегруженный метод нужно вызвать.

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

Иногда перегруженные методы имеют разные имена аргументов, и в этом случае вы также можете дать компилятору ключ, называя аргументы. Ниже приведен пример конструктора 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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow