Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Uma função inline com parâmetros de tipo estaticamente resolvidos pode exigir um membro com um nome e assinatura específicos. O F# 11.2 permite que um membro de extensão dentro do âmbito satisfaça esse requisito, mesmo quando o tipo vem de outro assembly.
Use esta capacidade para separar um algoritmo genérico de adaptadores para tipos específicos. Não precisas de encapsular esses tipos ou adicionar uma interface nominal.
Note
No SDK .NET 11.0.100, ative <LangVersion>preview</LangVersion> onde o compilador resolve extensões. Consulte as definições de idioma F# 11.2 para a banda de funcionalidades planeada. Os exemplos de caminho e mapeamento usam uma biblioteca F# 11.0 e um consumidor de pré-visualização; ver Compilar bibliotecas e consumidores.
Adaptar uma propriedade para um algoritmo partilhado
O seguinte código de aplicação mede caminhos representados por dois tipos de frameworks existentes. Ambos os caminhos têm um comprimento 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
A fonte da biblioteca define Structural.Adapters e Structural.Algorithms. Os adaptadores expõem a mesma propriedade de coordenadas em tipos com diferentes APIs 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
O algoritmo pede uma Coordinates propriedade que devolve um par de valores de ponto flutuante:
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))
Em cada chamada concreta, o compilador seleciona um Coordinates membro, chamado testemunha da restrição. O algoritmo em linha aceita uma propriedade intrínseca ou de extensão; caminhos vazios e de ponto único têm comprimento zero.
Partilhar uma operação entre tipos de coleções
As restrições de extensão também suportam membros estáticos. O módulo da Structural.Adapters biblioteca define adaptadores usando as funções e existentes Option.mapList.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
No módulo da Structural.Algorithms biblioteca, peça a um Map membro sobre o tipo de entrada completo:
let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
(^Input :
(static member Map: ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
A aplicação pode então partilhar uma transformação, alterar o tipo de elemento e compor mapeamento sobre uma coleção aninhada:
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]
O compilador verifica os nomes e tipos dos membros, não interfaces ou leis de mapeamento. Isto não é polimorfismo de tipo superior: nenhum parâmetro representa um construtor de contentores como F<_>.
Extensões de operadores
Os operadores podem usar o mesmo mecanismo. Por exemplo, defina um operador de mapeamento para listas e opções:
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))
Um consumidor abre StructuralOperators e utiliza o 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
As soluções de operadores primitivos incorporadas mantêm prioridade para tipos de correspondência. Uma extensão não fornece alargamento numérico misto arbitrário nem substitui todos os operadores incorporados.
O modo F# 11.0 pode aceitar uma declaração comum de operador de extensão com FS1215, mas não pode usá-la como solução SRTP. O projeto do operador utiliza pré-visualização.
Operadores de tuplas e contexto
Uma coleção de observações e um par de resultados acumulados independentemente podem participar no mesmo protocolo combinado. O seguinte consumidor define o seu algoritmo genérico após abrir os adaptadores da 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 extensões de biblioteca dão ao operador um significado para inteiros, listas e 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 requer uma lista não vazia porque utiliza List.reduce. A operação preserva a ordem da lista; o compilador não impõe associatividade nem fornece uma identidade para uma entrada vazia.
Cabeçalhos type ('T1 * 'T2) with nativos e type struct ('T1 * 'T2) with extensão das tuplas de referência e valor CLR. Ambos são funcionalidades F# 11.2 e suportam elementos de dois a sete; cabeçalhos maiores produzem FS3915.
Tuplas também podem conter contexto juntamente com um valor. O operador seguinte acrescenta um trilho enquanto aplica uma função 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)
Chamadas de instância Tuple dot e cabeçalhos nativos de função como type ('a -> 'b) with não são suportados.
Tanto a biblioteca de composições como o utilizador usam pré-visualização. Mantenha combineAll o consumidor: exportar essa redução pode produzir FS0193 para a tupla ++. Os exemplos separados de caminho e mapeamento exportam os seus algoritmos.
Construção dirigida pelo retorno
Para uma operação que introduz um valor simples num contentor, o tipo de saída pode identificar qual o elemento que deve construí-lo:
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"
A anotação do resultado do consumidor seleciona a extensão do Return tipo de saída. Só a entrada não consegue escolher um contentor.
Cada contentor fornece o seu próprio membro. Para sobrecargas apenas de retorno num único tipo, use o atributo separadoAllowOverloadOnReturnType.
Compilar bibliotecas e consumidores
As definições linguísticas aplicam-se por projeto, não transitivamente através de referências. A matriz seguinte utiliza as definições do SDK .NET 11.0.100 para funcionalidades do F# 11.2:
| Implementação da biblioteca | Utilização pelo consumidor | Requisito de pré-visualização |
|---|---|---|
| Declara cabeçalhos nativos de extensão de tupla. | Resolve os seus operadores de extensão. | Ambos precisam de pré-visualização; a nova sintaxe do cabeçalho em si é pré-visualização. |
| Define extensões ordinárias e exporta uma restrição de membro inline não resolvida. | Fornece tipos de betão cujas extensões resolvem essa restrição. | Pré-visualização das necessidades do consumidor. A biblioteca pode usar F# 11.0. |
| Resolve internamente uma restrição de extensão e expõe uma API inline ordinária ou já resolvida. | Chama essa API sem resolução adicional de extensão. | A biblioteca precisa de pré-visualização; o consumidor pode usar F# 11.0. |
| Resolve restrições de extensões e exportações que o consumidor também resolve através de extensões. | Resolve as restrições restantes. | Ambos precisam de pré-visualização. |
A biblioteca de caminho e mapeamento usa 11.0; o seu consumidor usa preview. A partir do diretório de consumidor, executa dotnet run com um SDK F#11 de suporte.
Âmbito de extensão entre assemblagens
Use extensões públicas e abra o seu módulo antes de definir funções dependentes em linha. Para uma restrição importada não resolvida, também abrir os adaptadores no consumidor; as declarações do open produtor não são transferidas. Uma API já resolvida não exige que o consumidor reabra os seus adaptadores.
Inferência e assinaturas
A resolução de F# 11.2 adia algumas escolhas que o modo F# 11.0 faz anteriormente. Considere um assistente inline definido com um DateTime recetor:
let inline shift (date: System.DateTime) offset = date + offset
No modo F# 11.0, esta forma pode inferir DateTime -> TimeSpan -> DateTime. Com a resolução de extensão F# 11.2 ativada, pode manter uma restrição para (DateTime or ^Offset) : (static member (+) : DateTime * ^Offset -> ^Result). Um consumidor pode então selecionar uma extensão pública dentro do âmbito para um tipo de offset diferente.
Um consumidor não pode recuperar a generalidade perdida numa biblioteca F# 11.0. Verifique as assinaturas exportadas; o produtor pode também precisar de ter a inferência F# 11.2 ativada.
Limitações de pré-visualização e resolução de problemas
- Para FS0001 ou FS0193, verifique as definições de idioma, visibilidade pública e o âmbito do adaptador.
- Se um ficheiro produtor posterior resolver o exemplo de caminho para
Complex, a geração de código pode falhar com FS0971,Undefined value 'get_Coordinates'. Mantém essa resolução com as suas definições ou num consumidor separado. - Adaptadores concorrentes podem tornar a escolha dos membros ambígua.