Buscar..


Introducción a los tipos

Los tipos pueden representar varios tipos de cosas. Puede ser un solo dato, un conjunto de datos o una función.

En F #, podemos agrupar los tipos en dos categorías:

  • Tipos 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
    
  • Tipos .NET

    • Tipo incorporado (int, bool, string, ...)
    • Clases, Estructuras e Interfaces
    • Delegados
    • Arrays

Escriba las abreviaturas

Las abreviaturas de tipo le permiten crear alias en los tipos existentes para darles un sentido más significativo.

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

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

Entonces puedes usar el alias como cualquier otro tipo:

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

Tenga cuidado, los alias no comprueban la consistencia del tipo. Esto significa que dos alias que se dirigen al mismo tipo pueden asignarse entre sí:

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

Los tipos se crean en F # usando la palabra clave de tipo

F# usa la palabra clave de type para crear diferentes tipos de tipos.

  1. Tipo de alias
  2. Sindicatos discriminados.
  3. Tipos de registro
  4. Tipos de interfaz
  5. Tipos de clase
  6. Tipos de struct

Ejemplos con código C# equivalente cuando sea posible:

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

Inferencia de tipos

Reconocimiento

Este ejemplo es una adaptación de este artículo sobre la inferencia de tipos.

¿Qué es el tipo de inferencia?

Inferencia de tipos es el mecanismo que permite al compilador deducir qué tipos se usan y dónde. Este mecanismo se basa en un algoritmo a menudo llamado "Hindley-Milner" o "HM". Vea a continuación algunas de las reglas para determinar los tipos de valores simples y de función:

  • Mira los literales
  • Mira las funciones y otros valores con los que algo interactúa.
  • Mira cualquier restricción de tipo explícito
  • Si no hay restricciones en ninguna parte, generalice automáticamente a tipos genéricos

Mira los literales

El compilador puede deducir tipos mirando los literales. Si el literal es un int y usted le agrega una "x", entonces "x" también debe ser un int. Pero si el literal es un flotador y le está agregando una "x", entonces "x" también debe ser un flotador.

Aquí hay unos ejemplos:

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"

Mira las funciones y otros valores con los que interactúa.

Si no hay literales en ninguna parte, el compilador intenta resolver los tipos analizando las funciones y otros valores con los que interactúan.

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

Mira cualquier restricción de tipo explícito o anotaciones

Si se especifican restricciones de tipo explícitas o anotaciones, entonces el compilador las usará.

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

Generalización automática

Si después de todo esto, no se encuentran restricciones, el compilador simplemente hace que los tipos sean genéricos.

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

Cosas que pueden ir mal con la inferencia de tipos.

La inferencia de tipos no es perfecta, por desgracia. A veces el compilador simplemente no tiene ni idea de qué hacer. Nuevamente, entender lo que está sucediendo realmente te ayudará a mantener la calma en lugar de querer matar al compilador. Estas son algunas de las principales razones de los errores de tipo:

  • Declaraciones fuera de orden
  • No hay suficiente información
  • Métodos sobrecargados

Declaraciones fuera de orden

Una regla básica es que debe declarar las funciones antes de que se utilicen.

Este código falla:

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

Pero esto está bien:

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

Declaraciones recursivas o simultáneas.

Una variante del problema "fuera de servicio" ocurre con funciones recursivas o definiciones que tienen que referirse entre sí. Ninguna cantidad de reordenamiento ayudará en este caso, necesitamos usar palabras clave adicionales para ayudar al compilador.

Cuando se está compilando una función, el identificador de la función no está disponible para el cuerpo. Entonces, si define una función recursiva simple, obtendrá un error del compilador. La solución es agregar la palabra clave "rec" como parte de la definición de la función. Por ejemplo:

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

Aquí está la versión fija con "rec fib" agregada para indicar que es recursiva:

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

No hay suficiente información

A veces, el compilador simplemente no tiene suficiente información para determinar un tipo. En el siguiente ejemplo, el compilador no sabe en qué tipo se supone que funciona el método Length. Pero tampoco puede hacer que sea genérico, así que se queja.

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

Este tipo de error se puede solucionar con anotaciones explícitas.

let stringLength (s:string) = s.Length

Métodos sobrecargados

Al llamar a una clase o método externo en .NET, a menudo recibirá errores debido a la sobrecarga.

En muchos casos, como el siguiente ejemplo de concat, tendrá que anotar explícitamente los parámetros de la función externa para que el compilador sepa a qué método sobrecargado llamar.

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

A veces, los métodos sobrecargados tienen diferentes nombres de argumento, en cuyo caso también puede dar una pista al compilador al nombrar los argumentos. Aquí hay un ejemplo para el constructor 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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow