Поиск…
Введение в типы
Типы могут представлять разные вещи. Это могут быть отдельные данные, набор данных или функция.
В 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
для создания различных типов типов.
- Тип псевдонимов
- Типы дискриминационного союза
- Типы записей
- Типы интерфейсов
- Типы классов
- Типы типов
Примеры с эквивалентным кодом 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