Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Una función insertada con parámetros de tipo resueltos estáticamente puede requerir un miembro con un nombre y una firma concretos. F# 11.2 permite que un miembro de extensión dentro del ámbito cumpla ese requisito, incluso cuando el tipo procede de otro ensamblado.
Use esta funcionalidad para separar un algoritmo genérico de adaptadores para tipos concretos. No es necesario encapsular esos tipos ni agregarles una interfaz nominal.
Note
En el SDK de .NET 11.0.100, habilite <LangVersion>preview</LangVersion> donde el compilador resuelva las extensiones. Consulte Configuración del lenguaje F# 11.2 para la banda de características planeada. Los ejemplos de ruta de acceso y asignación usan una biblioteca F# 11.0 y un consumidor en versión preliminar; consulte Compilación de bibliotecas y consumidores.
Adaptación de una propiedad para un algoritmo compartido
El código de aplicación siguiente mide las rutas de acceso representadas por dos tipos de marco existentes. Ambas rutas de acceso tienen una longitud de 8:
open System.Numerics
open Structural.Adapters
open Structural.Algorithms
let complexRoute =
[ Complex(0.0, 0.0); Complex(3.0, 4.0); Complex(6.0, 4.0) ]
let vectorRoute =
[ Vector2(0.0f, 0.0f); Vector2(3.0f, 4.0f); Vector2(6.0f, 4.0f) ]
printn (pathLength complexRoute) // 8
printn (pathLength vectorRoute) // 8
El origen de la biblioteca define Structural.Adapters y Structural.Algorithms. Los adaptadores exponen la misma propiedad de coordenada en tipos con diferentes API nativas:
type System.Numerics.Complex with
member point.Coordinates = point.Real, point.Imaginary
type System.Numerics.Vector2 with
member point.Coordinates = float point.X, float point.Y
El algoritmo solicita una Coordinates propiedad que devuelve un par de valores de punto flotante:
let inline coordinates (point: ^Point) : float * float =
(^Point : (member Coordinates: float * float) point)
let inline pathLength (points: ^Point list) =
points
|> List.pairwise
|> List.sumBy (fun (a, b) ->
let ax, ay = coordinates a
let bx, by = coordinates b
let dx, dy = bx - ax, by - ay
sqrt (dx * dx + dy * dy))
En cada llamada concreta, el compilador selecciona un Coordinates miembro, denominado testigo de la restricción. El algoritmo insertado acepta una propiedad intrínseca o de extensión; las rutas de acceso vacías y de punto único tienen la longitud cero.
Uso compartido de una operación entre tipos de colección
Las restricciones de extensión también admiten miembros estáticos. El módulo de Structural.Adapters la biblioteca define adaptadores mediante las funciones y Option.map existentesList.map:
type List<'T> with
static member Map(source: 'T list, mapping: 'T -> 'U) : 'U list =
List.map mapping source
type Option<'T> with
static member Map(source: 'T option, mapping: 'T -> 'U) : 'U option =
Option.map mapping source
En el módulo de Structural.Algorithms la biblioteca, solicite un Map miembro en el tipo de entrada completo:
let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
(^Input :
(static member Map: ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
Después, la aplicación puede compartir una transformación, cambiar el tipo de elemento y crear la asignación a través de una colección anidada:
let inline cleanNames values =
map (fun (name: string) -> name.Trim().ToUpperInvariant()) values
let batch: string list = cleanNames [ " Ada "; " grace " ]
let optional: string option = cleanNames (Some " linus ")
let missing: string option = cleanNames (None: string option)
let lengths: int list = map String.length batch
let nested: int option list = map (map String.length) [ Some "abc"; None ]
printfn "%A; %A; %A" batch optional missing
printfn "%A; %A" lengths nested
// ["ADA"; "GRACE"]; Some "LINUS"; None
// [3; 5]; [Some 3; None]
El compilador comprueba los nombres y tipos de miembros, no interfaces ni leyes de asignación. Esto no es polimorfismo de tipo superior: ningún parámetro representa un constructor de contenedor como F<_>.
Extensiones de operador
Los operadores pueden usar el mismo mecanismo. Por ejemplo, defina un operador de asignación para listas y opciones:
type List<'T> with
static member (|>>) (source: 'T list, mapping: 'T -> 'U) =
List.map mapping source
type Option<'T> with
static member (|>>) (source: 'T option, mapping: 'T -> 'U) =
Option.map mapping source
let inline (|>>) (source: ^Input) (mapping: 'T -> 'U) : ^Output =
(^Input :
(static member (|>>): ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
Se abre StructuralOperators un consumidor y usa el operador :
open StructuralOperators
let incremented: int list = [1; 2; 3] |>> ((+) 10)
let length: int option = Some "hello" |>> String.length
// [11; 12; 13] and Some 5
Las soluciones de operador primitivo integradas conservan la prioridad para los tipos coincidentes. Una extensión no proporciona una ampliación numérica mixta arbitraria ni reemplaza todos los operadores integrados.
El modo F# 11.0 puede aceptar una declaración normal de operador de extensión con FS1215, pero no puede usarla como solución SRTP. El proyecto de operador usa la vista previa.
Operadores de tupla y contexto
Una colección de observaciones y un par de resultados acumulados independientemente pueden participar en el mismo protocolo de combinación. El consumidor siguiente define su algoritmo genérico después de abrir los adaptadores de la biblioteca:
open Composition.Adapters
open Composition.Algorithms
let inline combineAll (batches: ^T list) : ^T =
List.reduce (++) batches
let observations = combineAll [["temperature"]; ["pressure"; "humidity"]]
let summary = combineAll [(2, ["temperature"]); (3, ["pressure"; "humidity"])]
printfn "%A" observations
printfn "%A" summary
// ["temperature"; "pressure"; "humidity"]
// (5, ["temperature"; "pressure"; "humidity"])
Estas extensiones de biblioteca proporcionan al operador un significado para enteros, listas y pares:
type System.Int32 with
static member (++) (left: int, right: int) = left + right
type List<'T> with
static member (++) (left: 'T list, right: 'T list) = left @ right
type ('T1 * 'T2) with
static member inline (++) ((a, b), (c, d)) = (a ++ c, b ++ d)
combineAll requiere una lista no vacía porque usa List.reduce. La operación conserva el orden de lista; El compilador no aplica la asociatividad ni proporciona una identidad para una entrada vacía.
Los encabezados nativos y type struct ('T1 * 'T2) with extienden las tuplas type ('T1 * 'T2) with de referencia y valor clR. Ambas son características de F# 11.2 y admiten dos a siete elementos; Los encabezados más grandes generan FS3915.
Las tuplas también pueden llevar contexto junto con un valor. El operador siguiente anexa un rastro al aplicar una función contextual:
let priced =
(["catalogue price"], fun units -> decimal units * 12m)
<*> (["validated quantity"], 3)
printfn "%A" priced
// (["catalogue price"; "validated quantity"], 36M)
type ('Context * 'Value) with
static member inline (<*>) ((before, mapping), (after, value)) =
(before ++ after, mapping value)
type struct ('Context * 'Value) with
static member inline (<*>) (struct (before, mapping), struct (after, value)) =
struct (before ++ after, mapping value)
No se admiten llamadas de instancia de punto de tupla y encabezados nativos de tipo de función, como type ('a -> 'b) with .
La biblioteca de composición y el consumidor usan la versión preliminar. Mantenga combineAll en el consumidor: exportar esa reducción puede producir FS0193 para la tupla ++. Los ejemplos de ruta de acceso y asignación independientes exportan sus algoritmos.
Construcción dirigida a retorno
Para una operación que introduce un valor sin formato en un contenedor, el tipo de salida puede identificar qué miembro debe construirlo:
type List<'T> with
static member Return(value: 'T) : 'T list = [value]
type Option<'T> with
static member Return(value: 'T) : 'T option = Some value
let inline singleton (value: 'T) : ^Output =
(^Output : (static member Return: 'T -> ^Output) value)
let oneBatch: string list = singleton "ready"
let oneOptional: string option = singleton "ready"
// ["ready"] and Some "ready"
La anotación de resultados del consumidor selecciona la extensión del tipo de Return salida. La entrada por sí sola no puede elegir un contenedor.
Cada contenedor proporciona su propio miembro. En el caso de las sobrecargas de solo devolución en un solo tipo, use el atributo independienteAllowOverloadOnReturnType.
Compilación de bibliotecas y consumidores
La configuración de idioma se aplica por proyecto, no transitivamente a través de referencias. En la matriz siguiente se usa la configuración del SDK de .NET 11.0.100 para las características de F# 11.2:
| Implementación de la biblioteca | Uso del consumidor | Requisito de versión preliminar |
|---|---|---|
| Declara encabezados de tupla-extension nativos. | Resuelve sus operadores de extensión. | Ambos necesitan vista previa; La nueva sintaxis de encabezado es una vista previa. |
| Define extensiones normales y exporta una restricción de miembro insertada sin resolver. | Proporciona tipos concretos cuyas extensiones resuelven esa restricción. | El consumidor necesita una vista previa. La biblioteca puede usar F# 11.0. |
| Resuelve una restricción de extensión internamente y expone una API insertada normal o ya resuelta. | Llama a esa API sin más resolución de extensión. | La biblioteca necesita una vista previa; el consumidor puede usar F# 11.0. |
| Resuelve las extensiones y las restricciones de exportación que el consumidor también resuelve a través de extensiones. | Resuelve las restricciones restantes. | Ambos necesitan una vista previa. |
La ruta de acceso y la biblioteca de asignación usa 11.0; su consumidor usa preview. En el directorio de consumidor, ejecute dotnet run con un SDK de F# 11 compatible.
Ámbito de extensión entre ensamblados
Use extensiones públicas y abra su módulo antes de definir funciones insertadas dependientes. Para una restricción no resuelta importada, abra también los adaptadores en el consumidor; Las declaraciones del open productor no se transfieren. Una API ya resuelta no requiere que el consumidor vuelva a abrir sus adaptadores.
Inferencia y firmas
La resolución F# 11.2 aplaza algunas opciones que el modo F# 11.0 realiza anteriormente. Considere un asistente insertado definido con un DateTime receptor:
let inline shift (date: System.DateTime) offset = date + offset
En el modo F# 11.0, este formulario puede deducir DateTime -> TimeSpan -> DateTime. Con la resolución de extensión F# 11.2 habilitada, puede conservar una restricción para (DateTime or ^Offset) : (static member (+) : DateTime * ^Offset -> ^Result). A continuación, un consumidor puede seleccionar una extensión pública en el ámbito para un tipo de desplazamiento diferente.
Un consumidor no puede recuperar la generalidad perdida en una biblioteca F# 11.0. Comprobar firmas exportadas; El productor también podría necesitar la inferencia F# 11.2 habilitada.
Limitaciones y solución de problemas de la versión preliminar
- Para FS0001 o FS0193, compruebe la configuración del idioma, la visibilidad pública y el ámbito del adaptador.
- Si un archivo de productor posterior resuelve la ruta de acceso de ejemplo para
Complex, la generación de código puede producir un error con FS0971,Undefined value 'get_Coordinates'. Mantenga esa resolución con sus definiciones o en un consumidor independiente. - Los adaptadores competidores pueden hacer que la selección de miembros sea ambigua.