Conversiones (F#)

En este artículo se describe la compatibilidad con las conversiones de tipos en F#.

Tipos aritméticos

F# proporciona operadores de conversión para conversiones aritméticas entre varios tipos primitivos, como entre tipos enteros y de punto flotante. Los operadores de conversión integral y char tienen formatos comprobados y sin comprobar; los operadores de punto flotante y el operador de conversión enum no tienen. Los formatos sin comprobar se definen en FSharp.Core.Operators y los formatos comprobados se definen en FSharp.Core.Operators.Checked. Los formatos comprobados comprueban si hay desbordamiento y generan una excepción en tiempo de ejecución si el valor resultante supera los límites del tipo de destino.

Cada uno de estos operadores tiene el mismo nombre que el nombre del tipo de destino. Por ejemplo, en el código siguiente, en el que los tipos se anotan explícitamente, byte aparece con dos significados diferentes. La primera aparición es el tipo y el segundo es el operador de conversión.

let x : int = 5

let b : byte = byte x

En la tabla siguiente se muestran los operadores de conversión definidos en F#.

Operador Descripción
byte Convertir en byte, un tipo sin signo de 8 bits.
sbyte Convertir en byte con signo.
int16 Convertir en un entero de 16 bits con signo.
uint16 Convertir en un entero de 16 bits sin signo.
int32, int Convertir en un entero de 32 bits con signo.
uint32 Convertir en un entero de 32 bits sin signo.
int64 Convertir en un entero de 64 bits con signo.
uint64 Convertir en un entero de 64 bits sin signo.
nativeint Convertir en un entero nativo.
unativeint Convertir en un entero nativo sin signo.
float, double Convertir en un número de punto flotante IEEE de precisión doble de 64 bits.
float32, single Convertir en un número de punto flotante IEEE de precisión sencilla de 32 bits.
decimal Convertir en System.Decimal.
char Convertir en System.Char, un carácter Unicode.
enum Convertir en un tipo enumerado.

Además de los tipos primitivos integrados, puede usar estos operadores con tipos que implementan métodos op_Explicit o op_Implicit con firmas adecuadas. Por ejemplo, el operador de conversión int funciona con cualquier tipo que proporciona un método estático op_Explicit que toma el tipo como parámetro y devuelve int. Como excepción especial a la regla general de que los métodos no se pueden sobrecargar por tipo de valor devuelto, puede hacerlo para op_Explicit y op_Implicit.

Tipos enumerados

El operador enum es un operador genérico que toma un parámetro de tipo que representa el tipo de enum que se va a convertir. Cuando se convierte en un tipo enumerado, la inferencia de tipos intenta determinar el tipo de enum al que desea convertir. En el ejemplo siguiente, la variable col1 no se anota explícitamente, pero su tipo se infiere de la prueba de igualdad posterior. Por lo tanto, el compilador puede deducir que está convirtiendo en una enumeración Color. Como alternativa, puede proporcionar una anotación de tipo, como col2 del ejemplo siguiente.

type Color =
    | Red = 1
    | Green = 2
    | Blue = 3

// The target type of the conversion cannot be determined by type inference, so the type parameter must be explicit.
let col1 = enum<Color> 1

// The target type is supplied by a type annotation.
let col2 : Color = enum 2

También puede especificar el tipo de enumeración de destino explícitamente como parámetro de tipo, como en el código siguiente:

let col3 = enum<Color> 3

Tenga en cuenta que las conversiones de enumeración solo funcionan si el tipo subyacente de la enumeración es compatible con el tipo que se está convirtiendo. En el código siguiente, la conversión no se puede compilar debido a la falta de coincidencia entre int32 y uint32.

// Error: types are incompatible
let col4 : Color = enum 2u

Para más información, vea Enumeraciones.

Tipos de objetos de conversión

La conversión entre tipos de una jerarquía de objetos es fundamental para la programación orientada a objetos. Hay dos tipos básicos de conversiones: la conversión a tipo básico y la conversión a tipo heredado. La conversión a tipo básico de una jerarquía significa convertir desde una referencia de objeto derivado a una referencia de objeto base. Se garantiza el funcionamiento de dicha conversión siempre que la clase base esté en la jerarquía de herencia de la clase derivada. La conversión a tipo heredado de una jerarquía, desde una referencia de objeto base a una referencia de objeto derivado, solo se realiza correctamente si el objeto es realmente una instancia del tipo de destino correcto (derivado) o un tipo derivado del tipo de destino.

F# proporciona operadores para estos tipos de conversiones. El operador :> convierte la jerarquía a tipo básico y el operador :?> convierte la jerarquía a tipo heredado.

Conversión a tipo básico

En muchos lenguajes orientados a objetos, la conversión a tipo básico está implícita; en F#, las reglas son ligeramente diferentes. La conversión a tipo básico se aplica automáticamente cuando se pasan argumentos a métodos sobre un tipo de objeto. Sin embargo, para las funciones enlazadas por let en un módulo, la conversión a tipo básico no es automática, a menos que el tipo de parámetro se declare como un tipo flexible. Para más información, vea Tipos flexibles.

El operador :> realiza una conversión estática, lo que significa que el éxito de la conversión se determina en tiempo de compilación. Si una conversión que usa :> se compila correctamente, es una conversión válida y no tiene ninguna posibilidad de error en tiempo de ejecución.

También puede usar el operador upcast para realizar dicha conversión. La expresión siguiente especifica una conversión a tipo básico de la jerarquía:

upcast expression

Cuando se usa el operador upcast, el compilador intenta inferir el tipo al que se va a convertir a partir del contexto. Si el compilador no puede determinar el tipo de destino, el compilador notifica un error. Es posible que se requiera una anotación de tipo.

Conversión a tipo heredado

El operador :?> realiza una conversión dinámica, lo que significa que el éxito de la conversión se determina en tiempo de ejecución. Una conversión que usa el operador :?> no se comprueba en tiempo de compilación; pero en tiempo de ejecución, se intenta convertir al tipo especificado. Si el objeto es compatible con el tipo de destino, la conversión se realiza correctamente. Si el objeto no es compatible con el tipo de destino, el tiempo de ejecución genera una excepción InvalidCastException.

También puede usar el operador downcast para realizar una conversión dinámica de tipos. La siguiente expresión especifica una conversión de la jerarquía a un tipo heredado que se infiere del contexto del programa:

downcast expression

En cuanto al operador upcast, si el compilador no puede inferir un tipo de destino específico a partir del contexto, notifica un error. Es posible que se requiera una anotación de tipo.

El siguiente código muestra el uso de los operadores :> y :?>. El código muestra que el operador :?> se usa mejor cuando se sabe que la conversión se realizará correctamente, ya que produce una excepción InvalidCastException si se produce un error en la conversión. Si no sabe si una conversión se realizará correctamente, es mejor una prueba de tipo que use una expresión match porque evita la sobrecarga de generar una excepción.

type Base1() =
    abstract member F : unit -> unit
    default u.F() =
     printfn "F Base1"

type Derived1() =
    inherit Base1()
    override u.F() =
      printfn "F Derived1"


let d1 : Derived1 = Derived1()

// Upcast to Base1.
let base1 = d1 :> Base1

// This might throw an exception, unless
// you are sure that base1 is really a Derived1 object, as
// is the case here.
let derived1 = base1 :?> Derived1

// If you cannot be sure that b1 is a Derived1 object,
// use a type test, as follows:
let downcastBase1 (b1 : Base1) =
   match b1 with
   | :? Derived1 as derived1 -> derived1.F()
   | _ -> ()

downcastBase1 base1

Dado que los operadores genéricos downcast y upcast se basan en la inferencia de tipos para determinar el argumento y el tipo de valor devuelto, puede reemplazar let base1 = d1 :> Base1 en el ejemplo de código anterior por let base1: Base1 = upcast d1.

Se requiere una anotación de tipo, porque el operador upcast por sí solo no pudo determinar la clase base.

Conversiones a tipo básico implícitas

Las conversiones a tipo básico implícitas se insertan en las situaciones siguientes:

  • Al proporcionar un parámetro a una función o método con un tipo con nombre conocido. Esto incluye cuando una construcción, como expresiones de cálculo o segmentación, se convierte en una llamada de método.

  • Al asignar o mutar un campo de registro o una propiedad que tiene un tipo con nombre conocido.

  • Cuando una rama de una expresión if/then/else o match tiene un tipo de destino conocido derivado de otra rama o tipo conocido general.

  • Cuando un elemento de una expresión de secuencia, matriz o lista tiene un tipo de destino conocido.

Por ejemplo, considere el siguiente código:

open System
open System.IO

let findInputSource () : TextReader =
    if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
        // On Monday a TextReader
        Console.In
    else
        // On other days a StreamReader
        File.OpenText("path.txt")

Aquí las ramas de la condición procesan TextReader y StreamReader respectivamente. En la segunda rama, el tipo de destino conocido es TextReader de la anotación de tipo en el método y de la primera rama. Esto significa que no se necesita ninguna conversión a tipo básico en la segunda rama.

Para mostrar una advertencia en cada punto se usa una conversión a tipo básico implícita adicional, puede habilitar la advertencia 3388 (/warnon:3388 o la propiedad <WarnOn>3388</WarnOn>).

Conversiones numéricas implícitas

F# usa la ampliación explícita de tipos numéricos en la mayoría de los casos a través de operadores de conversión. Por ejemplo, el ampliación explícita es necesaria para la mayoría de los tipos numéricos, como int8 o int16, o de float32 a float64, o cuando se desconoce el tipo de origen o de destino.

Sin embargo, la ampliación implícita se permite para enteros de 32 bits ampliados a enteros de 64 bits, en las mismas situaciones que en las conversiones a tipo básico implícitas. Por ejemplo, considere una forma de API típica:

type Tensor(…) =
    static member Create(sizes: seq<int64>) = Tensor(…)

Se pueden usar literales enteros para int64:

Tensor.Create([100L; 10L; 10L])

O literales enteros para int32:

Tensor.Create([int64 100; int64 10; int64 10])

La ampliación se produce automáticamente de int32 a int64, de int32 a nativeint y de int32 a double, cuando se conocen tanto el tipo de origen como el de destino durante la inferencia de tipos. Por lo tanto, en casos como los ejemplos anteriores, se pueden usar literales int32:

Tensor.Create([100; 10; 10])

También puede habilitar opcionalmente la advertencia 3389 (/warnon:3389 o propiedad <WarnOn>3389</WarnOn>) para mostrar una advertencia en cada punto en el que se usa la ampliación numérica implícita.

Conversiones implícitas de estilo .NET

Las API de .NET permiten la definición de métodos estáticos op_Implicit para proporcionar conversiones implícitas entre tipos. Se aplican automáticamente en el código de F# al pasar argumentos a métodos. Por ejemplo, considere el código siguiente que realiza llamadas explícitas a métodos op_Implicit:

open System.Xml.Linq

let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants(XName.op_Implicit "Item")

Las conversiones op_Implicit de estilo .NET se aplican automáticamente para las expresiones de argumentos cuando los tipos están disponibles para la expresión de origen y el tipo de destino:

open System.Xml.Linq

let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants("Item")

También puede habilitar opcionalmente la advertencia 3395 (/warnon:3395 o propiedad <WarnOn>3395</WarnOn>) para mostrar una advertencia en cada punto en el que se usa la conversión implícita de estilo .NET.

Las conversiones op_Implicit de estilo .NET también se aplican automáticamente para expresiones que no son de argumento de método en las mismas situaciones que en las conversiones a tipo básico implícitas. Sin embargo, cuando se usa amplia o inapropiadamente, las conversiones implícitas pueden interactuar incorrectamente con la inferencia de tipos y producir código más difícil de entender. Por este motivo, siempre generan advertencias cuando se usan en posiciones que no son de argumento.

Para mostrar una advertencia en cada punto en que una conversión implícita de estilo .NET se usa para un argumento que no es de método; puede habilitar la advertencia 3391 (/warnon:3391 o propiedad <WarnOn>3391</WarnOn>).

Se proporcionan las siguientes advertencias opcionales para usos de conversiones implícitas:

  • /warnon:3388 (conversión a tipo básico implícita adicional)
  • /warnon:3389 (ampliación numérica implícita)
  • /warnon:3391 (op_Implicit en argumentos que no son de método, activada de forma predeterminada)
  • /warnon:3395 (op_Implicit en argumentos de método)

Vea también