Por que você precisa de tipos mais altos?

13

Alguns idiomas permitem classes e funções com parâmetros de tipo (como List<T>onde Tpode ser um tipo arbitrário). Por exemplo, você pode ter uma função como:

List<S> Function<S, T>(List<T> list)

No entanto, alguns idiomas permitem que esse conceito seja estendido um nível mais alto, permitindo que você tenha uma função com a assinatura:

K<S> Function<K<_>, S, T>(K<T> arg)

Onde em K<_>si é um tipo como List<_>esse tem um parâmetro de tipo. Esse "tipo parcial" é conhecido como construtor de tipos.

Minha pergunta é: por que você precisa dessa capacidade? Faz sentido ter um tipo como, List<T>porque todos List<T>são quase exatamente iguais, mas todos K<_>podem ser totalmente diferentes. Você pode ter um Option<_>e um List<_>que não possuem nenhuma funcionalidade comum.

GregRos
fonte
7
Há várias respostas boas aqui: stackoverflow.com/questions/21170493/...
itsbruce
3
@itsbruce Em particular, o Functorexemplo da resposta de Luis Casillas é bastante intuitivo. O que tem List<T>e Option<T>tem em comum? Se você me der uma e uma função, T -> Seu posso lhe dar uma List<S>ou Option<S>. Outra coisa que eles têm em comum é que você pode tentar obter um Tvalor de ambos.
Doval
@Doval: Como você faria o primeiro? Na medida em que alguém se interessa pelo último, eu acho que isso poderia ser resolvido com a implementação de ambos os tipos IReadableHolder<T>.
Supercat
@supercat estou supondo que eles teriam de implementar IMappable<K<_>, T>com o método K<S> Map(Func<T, S> f), a implementação como IMappable<Option<_>, T>, IMappable<List<_>, T>. Então você teria que restringir K<T> : IMappable<K<_>, T>para tirar proveito disso.
GregRos
1
Elenco não é uma analogia apropriada. O que é feito com as classes de tipo (ou construções semelhantes) é que você mostra como um determinado tipo satisfaz o tipo de classificação mais alta. Isso geralmente envolve a definição de novas funções ou a exibição de quais funções existentes (ou métodos, se for uma linguagem OO como Scala) podem ser usadas. Uma vez feito isso, todas as funções definidas para trabalhar com o tipo superior funcionarão com esse tipo. Mas é muito mais do que uma interface, porque são definidos mais do que um simples conjunto de assinaturas de função / método. Acho que vou ter que escrever uma resposta para mostrar como isso funciona.
itsbruce

Respostas:

5

Como ninguém mais respondeu à pergunta, acho que vou tentar. Vou ter que ficar um pouco filosófico.

A programação genérica trata de abstrair tipos semelhantes, sem a perda de informações de tipo (o que acontece com o polimorfismo de valor orientado a objetos). Para fazer isso, os tipos devem necessariamente compartilhar algum tipo de interface (um conjunto de operações, não o termo OO) que você pode usar.

Nas linguagens orientadas a objetos, os tipos satisfazem uma interface em virtude de classes. Cada classe tem sua própria interface, definida como parte de seu tipo. Como todas as classes List<T>compartilham a mesma interface, você pode escrever um código que funcione independentemente da Tsua escolha. Outra maneira de impor uma interface é uma restrição de herança e, embora as duas pareçam diferentes, elas são parecidas se você pensar sobre isso.

Na maioria das linguagens orientadas a objetos, List<>não é um tipo adequado em si. Não possui métodos e, portanto, não possui interface. É só List<T>que tem essas coisas. Essencialmente, em termos mais técnicos, os únicos tipos que você pode abstrair significativamente são aqueles com esse tipo *. Para fazer uso de tipos mais avançados em um mundo orientado a objetos, você deve formular restrições de tipo de maneira consistente com essa restrição.

Por exemplo, conforme mencionado nos comentários, podemos visualizar Option<>eList<> como "mapeáveis", no sentido de que, se você tiver uma função, poderá converter um Option<T>em um Option<S>ou a List<T>em a List<S>. Lembrando que as classes não podem ser usadas para abstrair diretamente tipos mais altos, criamos uma interface:

IMappable<K<_>, T> where K<T> : IMappable<K<_>, T>

E então implementamos a interface em ambos List<T> e Option<T>como IMappable<List<_>, T>e IMappable<Option<_>, T>respectivamente. O que fizemos foi usar tipos de tipo mais alto para colocar restrições nos tipos reais (sem tipo mais alto) Option<T>e List<T>. É assim que é feito no Scala, embora o Scala tenha recursos como características, variáveis ​​de tipo e parâmetros implícitos que o tornam mais expressivo.

Em outros idiomas, é possível abstrair diretamente sobre tipos de tipos mais altos. Em Haskell, uma das maiores autoridades em sistemas de tipos, podemos codificar uma classe de tipo para qualquer tipo, mesmo que ela tenha um tipo mais alto. Por exemplo,

class Mappable mp where
    map :: mp a -> mp b

Essa é uma restrição colocada diretamente em um tipo (não especificado) mp que utiliza um parâmetro de tipo e requer que seja associado à função mapque transforma um mp<a>em um mp<b>. Podemos então escrever funções que restringem tipos de tipos mais altos, Mappableassim como nas linguagens orientadas a objetos, você pode colocar uma restrição de herança. Bem, mais ou menos.

Em resumo, sua capacidade de usar tipos de tipos mais altos depende de sua capacidade de restringi-los ou de usá-los como parte das restrições de tipo.

GregRos
fonte
Estive muito ocupado mudando de emprego, mas finalmente encontrarei algum tempo para escrever minha própria resposta. Eu acho que você se distraiu com a idéia de restrições. Um aspecto significativo dos tipos de classificação superior é que eles permitem definir funções / comportamentos que podem ser aplicados a qualquer tipo que possa ser mostrado logicamente para se qualificar. Longe de impor restrições aos tipos existentes, as classes de tipos (uma aplicação de tipos mais elevados) adicionam comportamento a elas.
#
Eu acho que é uma questão de semântica. Uma classe de tipo define uma classe de tipos. Ao definir uma função como (Mappable mp) => mp a -> mp b, você colocou uma restrição mppara ser um membro da classe de tipo Mappable. Quando você declara um tipo como Optionuma instância Mappable, você adiciona comportamento a esse tipo. Eu acho que você poderia usar esse comportamento localmente sem nunca restringir qualquer tipo, mas então não é diferente de definir uma função comum.
GregRos
Além disso, tenho certeza de que classes de tipo não estão diretamente relacionadas a tipos de classe superior. O Scala possui tipos de classificação mais alta, mas não classes de tipo, e você pode restringir as classes de tipo a tipos com o tipo *sem torná-los inutilizáveis. Definitivamente, é verdade que as classes de tipo são muito poderosas quando se trabalha com tipos de classe superior.
23915 GregRos
Scala tem classes de tipo. Eles são implementados com implícitos. Os implícitos fornecem o equivalente às instâncias da classe do tipo Haskell. É uma implementação mais frágil que a de Haskell, porque não há sintaxe dedicada para ela. Mas sempre foi um dos principais objetivos dos implícitos de Scala e eles fazem o trabalho.
itsbruce