BT

Disseminando conhecimento e inovação em desenvolvimento de software corporativo.

Contribuir

Tópicos

Escolha a região

Início Notícias Futuro do .NET: type classes e extensões

Futuro do .NET: type classes e extensões

Uma das funcionalidades sendo considerada para versões futuras do .NET é o suporte a type classes. Chamada de “shapes” na proposta Shapes and Extensions, a funcionalidade deixaria o .NET mais capaz no que diz respeito à generics. Mads Torgersen descreve type classes:

Interfaces são uma abstração da forma dos objetos. A ideia por trás de type classes é abstrair sobre os próprios tipos ao invés de sobre suas instâncias. Além disso, enquanto para um tipo implementar uma interface ele precisa fazê-lo em sua declaração, outra pessoa pode implementar um type class em um código separado.

Type classes solucionam um problema antigo das interfaces: elas não conseguem lidar com funções estáticas ou sobrecarga de operadores. Isso traz problemas como a necessidade de declarar a mesma função diversas vezes em bibliotecas matemáticas que lidam com diferentes tipos de dados numéricos.

Mads continua:

Em geral, a declaração de um "shape" é muito parecido com o de uma interface, exceto que:

  • Pode-se definir quase qualquer tipo de membro (incluindo membros estáticos);
  • Pode ser implementado por uma extensão;
  • Pode ser utilizado como um tipo somente em alguns lugares.

A última restrição é importante: um shape não é um tipo. Ao invés disso, a principal razão de um shape existir é ser utilizado como uma definição genérica, restringindo o tipo dos argumentos aos que possuem o shape correto, enquanto permitindo ao corpo da declaração genérica utilizar tal shape.

Ligada à ideia de shapes está uma melhora na sintaxe de extensão. Ao invés de apenas métodos, uma extensão poderia definir praticamente qualquer membro para um shape. Considere o seguinte exemplo:

public shape SNumber<T>
{
    static T operator +(T t1, T t2);
    static T operator -(T t1, T t2);
    static T Zero { get; }
}

O tipo Int32 já provê a maioria disso, mas lhe falta a propriedade Zero. Uma extensão pode corrigir isso:

public extension IntGroup of int : SNumber<int>
{
    public static int Zero => 0;
}

Você poderia então utilizá-lo da seguinte maneira:

public static AddAll<T>(T[] ts) where T : SNumber<t> // shape used as constraint
{
    var result = T.Zero; // Making use of the shape's Zero property
    foreach (var t in ts) { result += t; } // Making use of the shape's + operator
    return result;
}

Implementação

A implementação dessa funcionalidade requer algumas artimanhas com interfaces e structs.

  • Shapes são traduzidos para interfaces, com cada membro (até mesmo os estáticos) transformado em um membro da interface;
  • Extensões são traduzidas para structs, com cada membro (até mesmo os estáticos) transformados em um membro da struct;
  • Se a extensão implementa um ou mais shapes, então a struct utilizada por trás do mecanismo implementa as interfaces dos shapes.

A struct descrita acima é chamada de “witness struct”. Sua existência prova que a classe obedece as regras de um shape.

Segue aqui o mesmo método AddAll após tradução do compilador:

public static T AddAll<T, Impl>(T[] ts) where Impl : struct, SNumber<T>
{
    var impl = new Impl();
    var result = impl.Zero;
    foreach (var t in ts) { result = impl.op_Addition(result, t); }
    return result;
}

A witness struct mencionada anteriormente é então utilizada para prover a funcionalidade necessária ao método AddAll. A struct pode chamar os métodos diretamente no tipo ou utilizar a extensão necessária.

Implementando shapes em classes e interfaces

Classes podem implementar um shape de maneira explícita via a mesma sintaxe utilizada para estender classes base e implementar interfaces. O compilador irá prover a witness struct necessária.

Interfaces também podem ser marcadas como obedecendo os requisitos de um shape:

public extension Comparable<T> of IComparable<T> : SComparable<T>;

Já que existe uma correspondência exata entre IComparable<T> e nosso type class, não há necessidade de prover um corpo para a extensão.

Tipos genéricos

Tipos genéricos provam ter seus próprios problemas. Assim como em métodos genéricos, adicionar um shape ou type class como uma restrição à um tipo de uma classe genérica requer um parâmetro adicional. Já que a quantidade de parâmetros de uma classe genérica faz parte de seu nome, isso pode levar a colisões com outros tipos genéricos que possuem mesmo nome.

Extensões em shapes

Extensões não somente podem ser utilizadas para implementar shapes, mas também podem extendê-los.

Críticas

No geral, o feedback desta funcionalidade tem sido positivo. No entanto, algumas mudanças foram solicitadas. Por exemplo, shapes devem ser implementados de forma explícita no momento. Alguns desenvolvedores preferem que shapes seja implementados implicitamente pelo compilador caso não haja necessidade de métodos de extensão adicionais para uma dada classe ou interface. Mads cita alguns dos problemas disso:

Isso pode leva a geração de várias structs para um mesmo shape sendo aplicado a um mesmo tipo de uma mesma maneira, há então um risco de uma proliferação indevida de tipos gerados. Potencialmente isso pode ser mitigado pelo compilador sendo inteligente gerando apenas um por assembly, mas sabemos desde a funcionalidade de tipos anônimos que esse tipo de remoção de duplicidade é difícil e recheada de erros.

Também há preocupações sobre quão próximos shapes e extensões são relacionados. É cogitado que isso pode se provar confuso com o passar do tempo.

À isso Mads respondeu:

As extensões em minha proposta realmente misturam múltiplas preocupações:

...

Eu acho que há muito a ser dito sobre o mesmo mecanismo de uma linguagem servindo para vários propósitos - eles são, afinal, muito parecidos. Mas seria instrutivo ver uma proposta separando-os de maneira clara. Talvez nos leve a um cenário mais elegante.

Avalie esse artigo

Relevância
Estilo/Redação

Conteúdo educacional

BT