Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Uma função embutida com parâmetros de tipo resolvidos estaticamente pode exigir um membro com um nome e uma assinatura específicos. O F# 11.2 permite que um membro de extensão no escopo atenda a esse requisito, mesmo quando o tipo vem de outro assembly.
Use essa funcionalidade para separar um algoritmo genérico de adaptadores para tipos específicos. Você não precisa encapsular esses tipos ou adicionar uma interface nominal a eles.
Note
No SDK do .NET 11.0.100, habilite <LangVersion>preview</LangVersion> onde o compilador resolve extensões. Consulte as configurações de linguagem F# 11.2 para a faixa de recursos planejada. Os exemplos de caminho e mapeamento usam uma biblioteca F# 11.0 e um consumidor de visualização; consulte Compilar bibliotecas e consumidores.
Adaptar uma propriedade para um algoritmo compartilhado
O código do aplicativo a seguir mede caminhos representados por dois tipos de estrutura 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 origem da biblioteca define Structural.Adapters e Structural.Algorithms. Os adaptadores expõem a mesma propriedade de coordenadas em tipos com APIs nativas diferentes:
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 solicita uma Coordinates propriedade que retorna 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 embutido aceita uma propriedade intrínseca ou de extensão; caminhos vazios e de ponto único têm comprimento zero.
Compartilhar uma operação entre tipos de coleção
As restrições de extensão também dão suporte a membros estáticos. O módulo da Structural.Adapters biblioteca define adaptadores usando as funções existentesList.map:Option.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, solicite um Map membro no tipo de entrada completo:
let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
(^Input :
(static member Map: ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
Em seguida, o aplicativo pode compartilhar uma transformação, alterar o tipo de elemento e redigir o mapeamento em 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 nomes e tipos de membros, não interfaces ou leis de mapeamento. Isso não é polimorfismo de tipo superior: nenhum parâmetro representa um construtor de contêiner, como F<_>.
Extensões de operador
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 usa 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 operador primitivo internas retêm prioridade para tipos correspondentes. Uma extensão não fornece ampliação arbitrária de numérico misto ou substitui todos os operadores internos.
O modo F# 11.0 pode aceitar uma declaração de operador de extensão comum com FS1215, mas não pode usá-la como uma solução SRTP. O projeto do operador usa a versão prévia.
Operadores e contexto de tupla
Uma coleção de observações e um par de resultados acumulados independentemente podem participar do mesmo protocolo de combinação. O consumidor a seguir define seu algoritmo genérico depois de 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"])
Essas 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 íntegra porque ela usa List.reduce. A operação preserva a ordem de lista; o compilador não impõe associatividade nem fornece uma identidade para uma entrada vazia.
Cabeçalhos nativos type ('T1 * 'T2) with e type struct ('T1 * 'T2) with estender tuplas de referência e valor CLR. Ambos são recursos F# 11.2 e dão suporte a dois a sete elementos; cabeçalhos maiores produzem FS3915.
Tuplas também podem levar contexto junto com um valor. O operador a seguir acrescenta uma trilha ao aplicar 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)
Não há suporte para chamadas de tupla de instância de ponto e cabeçalhos nativos do tipo função, como type ('a -> 'b) with não há suporte.
A biblioteca de composição e o consumidor usam a versão prévia. Mantenha-se combineAll no consumidor: exportar essa redução pode produzir FS0193 para tupla ++. O caminho separado e os exemplos de mapeamento exportam seus algoritmos.
Construção direcionada ao retorno
Para uma operação que introduz um valor simples em um contêiner, o tipo de saída pode identificar qual membro 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 de resultado do consumidor seleciona a extensão do tipo de Return saída. A entrada por si só não pode escolher um contêiner.
Cada contêiner fornece seu próprio membro. Para sobrecargas somente de retorno em um único tipo, use o atributo separadoAllowOverloadOnReturnType.
Compilar bibliotecas e consumidores
As configurações de idioma se aplicam por projeto, não transitivamente por meio de referências. A matriz a seguir usa as configurações do SDK do .NET 11.0.100 para recursos do F# 11.2:
| Implementação da biblioteca | Uso do consumidor | Requisito de visualização |
|---|---|---|
| Declara cabeçalhos de extensão de tupla nativos. | Resolve seus operadores de extensão. | Ambos precisam de visualização; a sintaxe do novo cabeçalho em si é a visualização. |
| Define extensões comuns e exporta uma restrição de membro embutida não resolvida. | Fornece tipos concretos cujas extensões resolvem essa restrição. | O consumidor precisa de visualização. A biblioteca pode usar o F# 11.0. |
| Resolve uma restrição de extensão internamente e expõe uma API embutida comum ou já resolvida. | Chama essa API sem mais resolução de extensão. | A biblioteca precisa de visualização; o consumidor pode usar f# 11.0. |
| Resolve extensões e exporta restrições que o consumidor também resolve por meio de extensões. | Resolve as restrições restantes. | Ambos precisam de visualização. |
O caminho e a biblioteca de mapeamento usam 11.0; seu consumidor usa preview. No diretório do consumidor, execute dotnet run com um SDK F# 11 de suporte.
Escopo da extensão entre assemblies
Use extensões públicas e abra seu módulo antes de definir funções embutidas dependentes. Para uma restrição não resolvida importada, abra também 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 seus adaptadores.
Inferência e assinaturas
A resolução F# 11.2 adia algumas opções que o modo F# 11.0 faz anteriormente. Considere um auxiliar embutido definido com um DateTime receptor:
let inline shift (date: System.DateTime) offset = date + offset
No modo F# 11.0, esse formulário pode inferir DateTime -> TimeSpan -> DateTime. Com a resolução de extensão F# 11.2 habilitada, ela pode manter uma restrição para (DateTime or ^Offset) : (static member (+) : DateTime * ^Offset -> ^Result). Um consumidor pode selecionar uma extensão pública no escopo para um tipo de deslocamento diferente.
Um consumidor não pode recuperar a generalidade perdida em uma biblioteca F# 11.0. Verificar assinaturas exportadas; o produtor também pode precisar da inferência F# 11.2 habilitada.
Limitações de visualização e solução de problemas
- Para FS0001 ou FS0193, verifique as configurações de idioma, a visibilidade pública e o escopo do adaptador.
- Se um arquivo de produtor posterior resolver o exemplo de caminho para
Complex, a geração de código poderá falhar com FS0971Undefined value 'get_Coordinates'. Mantenha essa resolução com suas definições ou em um consumidor separado. - Adaptadores concorrentes podem tornar a seleção de membro ambígua.