Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Dorian Neto

O que são covariância e contravariância?

Recommended Posts

Fala pessoal, tudo tranquilo?

 

Venho estudando as features do PHP 7.2 e vi muito se falar de covariância e contravariância. Sei que esse conceito não é específico da linguagem PHP e sim de OO, mas escolhi postar aqui pois de certa forma a dúvida está ligada ao suporte da linguagem a esse conceito.

 

Dei uma pesquisada no google e até encontrei links bem relevantes, mas gostaria de abrir uma discussão para tentar fixar melhor o significado.

 

Desde já agradeço!

Compartilhar este post


Link para o post
Compartilhar em outros sites

Olá @Dorian Neto.

 

Covariância é a capacidade de você declarar o retorno de um método para um objeto X e após isso, declarar que uma classe filha possa retornar neste mesmo método uma variante do objeto (Y extends X, por exemplo).

Na prática é isso:

<?php
class X {}// uma classe
class Y extends X {}// uma variante daquela classe

class Foo {
    public function method() : X {}// retorna X
}

class Bar extends Foo {
    public function method() : Y {}// alteramos o retorno do método para retornar Y
}

 

Contravariância é a mesma coisa, mas para os parâmetros, ao invés do retorno. Veja:

<?php
class X {}// uma classe
class Y extends X {}// uma variante daquela classe

class Foo {
    public function method( X $param ) {}// recebe X
}

class Bar extends Foo {
    public function method( Y $param ) {}// alteramos o parametro para retornar Y
}

 

Isso seria lindo demais, cara. Há pouco tempo eu precisei exatamente desse recurso, e posso lhe mostrar para você ver na prática a utilidade disso. Veja esse código: https://forum.imasters.com.br/topic/562468-estrutura-de-um-projeto-de-marketing-multinível-com-php-e-mysql/?tab=comments#comment-2243078

 

No arquivo 2 - Mmn/DataStructures/Binary/Tree.php eu construi uma árvore binária.

O método push() dessa árvore recebe um Binary/Node.

E se eu resolvesse criar uma variação dessa árvore com algum comportamento diferente?

<?php
namespace Mmn\DataStructures\Multiary;

use Mmn\DataStructures\Binary;

class SuperTree extends Binary\Tree
{
    /**
     * Observe que esse Node não é Binary, mas Multiary
     * contrariando a definição de Binary\Tree (pai dessa classe)
     */
    public function push( Node $Node )
    {

    }
}

 

.... mas o fato é que o PHP não suporta esses recursos, nem mesmo na sua versão 7.2, então nenhum dos códigos acima funciona.

 

Mas afinal, pq isso virou assunto? Pq o PHP passou a suportar "meio caminho" para isso (que eu particularmente acho inútil para esse propósito, mas ao menos é mais um recurso na linguagem): um novo tipo chamado object.

 

Com isso você agora é capaz de definir parâmetros ou retornos completamente abstratos (mas que sejam objetos), assim:

<?php
// Código copiado do manual
// http://php.net/manual/pt_BR/migration72.new-features.php#migration72.new-features.object-type

function test(object $obj) : object // aqui está a novidade (parametro e retorno)
{
    return new SplQueue();
}

test(new StdClass());

// Esse funciona apenas no PHP 7.2

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

Ah, e quase esquecendo... agora há o suporte para Type Widening. Isso também é fundamental para complementar as informações do post anterior, pois dá espaço para a omissão do tipo de parâmetro. Veja o exemplo do manual:

<?php
interface A // também serve para classes
{
    public function Test(array $input);
}

class B implements A
{
    // tipo foi omitido, alterando a declaração do método na classe filho
    public function Test($input){}
}

 

Mas eu ainda acho que é apenas meio caminho para os recursos que você citou...

Compartilhar este post


Link para o post
Compartilhar em outros sites

@Matheus Tavares O que seria essas "declarações" que você faz dentro dos parâmetros dos métodos? Eu já vi algumas vezes isso, mas não sei o nome pra pesquisar sobre.

Exemplo:

public function Test(array $input);

public function push( Node $Node )

Compartilhar este post


Link para o post
Compartilhar em outros sites

@Matheus Tavares sensacional a sua resposta, muito obrigado!

 

Como fiz algumas pesquisas antes da sua resposta, acabei associando as definições que encontrei com a sua e isso me gerou mais dúvidas :P

 

Variância é a capacidade do método se transformar (tipos da entrada e saída) com o intuito de retornar um novo comportamento, isso eu entendi, mas a minha dúvida ainda é no conceito de covariância e contravariância. De acordo com o que eu pesquisei, meu entendimento final foi esse:

  • Covariância: é quando há uma sobrecarga em um método da subclasse onde o tipo do parâmetro ou retorno é transformado em um tipo igual ao do método da superclasse ou mais específico.
  • Contravariância: é o contrário da covariância, ou seja, quando há uma sobrecarga em um método da subclasse onde o tipo do parâmetro ou retorno é transformado em um tipo igual ao do método da superclasse ou mais genérico.

Exemplo de covariância:

<?php

class X
{
    public function foo(): object // tipo genérico
    {
        // ...
    }
}

class Y extends X
{
    public function foo(): string // tipo específico
    {
        // ...
    }
}

 

Exemplo de contravariância:

<?php

class X
{
    public function foo(array $param) // tipo específico
    {
        // ...
    }
}

class Y extends X
{
    public function foo(object $param) // tipo genérico
    {
        // ...
    }	
}

Isso está correto?

 

Covariância só se aplica a retorno e contravariância só se aplica a parâmetros?

 

Além disso, um método invariante quer dizer que o mesmo não sofreu nenhum tipo de transformação, correto?

 

Depois de refletir mais um pouco, pensei se esse conceito está associado diretamente com sobrecarga, recurso esse que o PHP ainda não tem (até tem, mas estranhamente o conceito é totalmente diferente das demais linguagens.). Se realmente essa reflexão que eu fiz com relação a associação de sobrecarga com variância estiver correta, você acha que é isso que falta para esse conceito ser aplicado de vez no PHP?

 

Mais uma vez, muito obrigado pelas resposta e me desculpe pela enxurrada de novos questionamentos :P

 

@BrunoBit o nome disso é Type Hiting. hoje em dia chamado de type declarations ou declaração de tipo ou indução de tipo. Segue o link na documentação: http://php.net/manual/pt_BR/functions.arguments.php#functions.arguments.type-declaration

Compartilhar este post


Link para o post
Compartilhar em outros sites

@Dorian Neto, em nome da praticidade eu acabei "mandando pelos ares" as definições. Falha minha.

 

4 horas atrás, Dorian Neto disse:

Variância é a capacidade do método se transformar (tipos da entrada e saída) com o intuito de retornar um novo comportamento, isso eu entendi

Perfeito, esse é o primeiro conceito. Só seria interessante dizer ainda que eles precisam se relacionar, ser variantes um do outro.

A questão é que a interpretação de variância é relativa de linguagem para linguagem.

Você deu o seguinte exemplo (vou escrever de forma resumida para encurtar a resposta):

class X
    method() : objeto // abstrato

class Y
    method() : string // específico

No contexto do PHP, string é específico de objeto? Não, mas em outras linguagens (como JS ou Java) sim.

No php, uma string é um tipo primitivo (não é um objeto). No JS uma string é um objeto, permitindo você fazer coisas como:

console.log( "minha string".length ); // output: 12

 

As definições que você explicou sobre covariância e contravariância estão certas (a respeito de genérico -> específico e vice-versa). Não preciso complementar nada aqui, apenas reiterar que os exemplos estão incorretos (ambos, por causa da invariância dos tipos).

A ideia é não quebrar as regras (de tipos) originalmente estabelecidas, mas permitir comportamentos variantes.

 

Veja a RFC (implementada na 7.2) a respeito do object typehint. Tem mais informações práticas sobre o assunto: https://wiki.php.net/rfc/object-typehint

 

4 horas atrás, Dorian Neto disse:

Além disso, um método invariante quer dizer que o mesmo não sofreu nenhum tipo de transformação, correto?

Correto, mas é bom dizer ainda que ele além de não ter sofrido, ele também não pode sofrer mutações.

Existem linguagens que implementam isso nativamente, mas nenhuma realmente popular.

Sabe quando as linguagens não oferecem atributos/métodos privados e os programadores nomeiam esses caras com um underscore no início para indicar esse comportamento? É exatamente isso que pode ser feito nesse caso.

Veja no Java. E uma biblioteca que faz uma implementação concreta e mais poderosa para isso no PHP: https://github.com/php-deal/framework.

 

5 horas atrás, Dorian Neto disse:

Covariância só se aplica a retorno e contravariância só se aplica a parâmetros?

Pra afirmar com "só", "sempre", "nunca", o ideal é ter um embasamento muito sólido te acompanhando, que não é o meu caso nesse tópico. Eu diria que sim, mas posso estar enganado.

 

5 horas atrás, Dorian Neto disse:

Depois de refletir mais um pouco, pensei se esse conceito está associado diretamente com sobrecarga

De fato existe uma relação, mas são coisas diferentes.

Você pode usar a sobrecarga como ferramenta para alcançar esses recursos, mas acho que dizer que estão "diretamente associados" é um pouco demais (mas na minha opinião isso é apenas uma verdade relativa, diferente do que temos como experiência do erro e do acerto em busca do conhecimento).

 

5 horas atrás, Dorian Neto disse:

Se realmente essa reflexão que eu fiz com relação a associação de sobrecarga com variância estiver correta, você acha que é isso que falta para esse conceito ser aplicado de vez no PHP?

Eu gosto muito do conceito de sobrecarga e realmente acho muito tosco aqueles métodos mágicos do PHP (apesar de interessante ter o recurso para alguns casos extremos). Se houvesse sobrecarga de fato no PHP, com certeza seria mais fácil aplicar tudo isso.

 

5 horas atrás, Dorian Neto disse:

e me desculpe pela enxurrada de novos questionamentos

Pode perguntar quantas vezes necessário, amigo. Estamos aqui para compartilhar conhecimento. Se eu não souber, alguém vai saber :)

Compartilhar este post


Link para o post
Compartilhar em outros sites
5 horas atrás, Dorian Neto disse:
  • Covariância: é quando há uma sobrecarga em um método da subclasse onde o tipo do parâmetro ou retorno é transformado em um tipo igual ao do método da superclasse ou mais específico.
  • Contravariância: é o contrário da covariância, ou seja, quando há uma sobrecarga em um método da subclasse onde o tipo do parâmetro ou retorno é transformado em um tipo igual ao do método da superclasse ou mais genérico.

 

 

Tirando que é uma sobreescrita (override) e não uma sobrecarga (overload), isso é verdade para a implementação no PHP. Mas só, e somente só, pelo motivo de covariância ser implementada somente para retorno de método e contravariância ser implementada somente para atributos de métodos no PHP.

 

A definição geral é ainda mais simples:

- Covariância: permitir o uso de um objeto mais específico no lugar de um mais genérico;

Contravariância: permitir o uso de um objeto mais genérico no lugar de um mais específico.

 

No caso de sobreescrita (override), é chamado de:

- Covariância/Contravariância de tipo de retorno de método (Covariant/Contravariant method return type);

- Covariância/Contravariância de tipo de argumento de método (Covariant/Contravariant method argument type).

 

O que o PHP está implementado é o seguinte (de forma bem básica, através do uso do tipo object):

- Covariância de tipo de retorno de método;

- Contravariância de tipo de argumento de método.

 

Por que ele não implementa ambos?

 

A resposta é bem simples, fere o Princípcio de Substituição de Liskov. De qualquer forma, existem poucas linguagens que implementam para ambos. Mas ai já não vem ao caso.

 

Existem outros tipos de implementações além de métodos?

 

Sim, pode verificar em C# com uso de Generics. 

https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance

Compartilhar este post


Link para o post
Compartilhar em outros sites

@Matheus Tavares e @Gabriel Heming vocês foram mais do que sensacionais! Depois de todas essas explicações não me resta mais dúvidas.

 

Obrigado pelas correções, eu realmente me equivoquei em algumas coisas :P

 

Abração!

Compartilhar este post


Link para o post
Compartilhar em outros sites

  • Conteúdo Similar

    • Por violin101
      Caros amigos, saudações.
       
      Por favor, me permita tirar uma dúvida com os amigos.

      Tenho um Formulário onde o Usuário digita todos os Dados necessários.

      Minha dúvida:
      --> como faço após o usuário digitar os dados e salvar, o Sistema chamar uma Modal ou mensagem perguntando se deseja imprimir agora ?

      Grato,
       
      Cesar
    • Por Carcleo
      Tenho uma abela de usuarios e uma tabela de administradores e clientes.
      Gostaria de uma ajuda para implementar um cadastro
       
      users -> name, login, passord (pronta) admins -> user_id, registratiom, etc.. client -> user_id, registratiom, etc...
      Queria ajuda para extender de user as classes Admin e Client
      Olhem como estáAdmin
      <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Admin extends User {     use HasFactory;            protected $fillable = [         'name',         'email',         'password',         'registration'     ];      private string $registration;     public function create(         string $name,          string $email,          string $password,         string $registration     )     {         //parent::create(['name'=>$name, 'email'=>$email, 'password'=>$password]);         parent::$name = $name;         parent::$email = $email;         parent::$password = $password;         $this->registration = $registration;     } } User
      <?php namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class User extends Authenticatable {     /** @use HasFactory<\Database\Factories\UserFactory> */     use HasFactory, Notifiable;     static string $name;     static string $email;     static string $password;     /**      * The attributes that are mass assignable.      *      * @var list<string>      */     protected $fillable = [         'name',         'email',         'password',     ];          /**      * The attributes that should be hidden for serialization.      *      * @var list<string>      */     protected $hidden = [         'remember_token',     ];     /**      * Get the attributes that should be cast.      *      * @return array<string, string>      */     protected function casts(): array     {         return [             'email_verified_at' => 'datetime',             'password' => 'hashed',         ];     }          public function roles() : BelongsToMany {         return $this->belongsToMany(Role::class);     }       public function hasHole(Array $roleName): bool     {                 foreach ($this->roles as $role) {             if ($role->name === $roleName) {                 return true;             }         }         return false;     }         public function hasHoles(Array $rolesName): bool     {                 foreach ($this->roles as $role) {             foreach ($rolesName as $rolee) {             if ($role->name === $rolee) {                 return true;             }          }         }         return false;     }         public function hasAbility(string $ability): bool     {         foreach ($this->roles as $role) {             if ($role->abilities->contains('name', $ability)) {                 return true;             }         }         return false;     }     } Como gravar um Admin na tabela admins sendo que ele é um User por extensão?
      Tentei assim mas é claro que está errado...
      public function store(Request $request, Admin $adminModel) {         $dados = $request->validate([             "name" => "required",             "email" => "required|email",             "password" => "required",             "registration" => "required"         ]);         $dados["password"] =  Hash::make($dados["password"]);                  $admin = Admin::where("registration",  $dados["registration"])->first();                  if ($admin)              return                    redirect()->route("admin.new")                             ->withErrors([                                 'fail' => 'Administrador já cadastrados<br>, favor verificar!'                   ]);                            $newAdmin = $adminModel->create(                                    $dados['name'],                                    $dados['email'],                                    $dados['password'],                                    $dados['registration']                                 );         dd($newAdmin);         $adminModel->save();         //$adminModel::create($admin);                  return redirect()->route("admin.new")->with("success",'Cadastrado com sucesso');     }  
    • Por violin101
      Caros amigos, saudações.
       
      Gostaria de tirar uma dúvida com os amigos, referente a PDV.
       
      Estou escrevendo um Sistema com Ponto de Vendas, a minha dúvida é o seguinte, referente ao procedimento mais correto.

      Conforme o caixa vai efetuando a venda, o Sistema de PDV já realiza:
      a baixa direto dos produtos no estoque
      ou
      somente após concretizar a venda o sistema baixa os produtos do estoque ?
       
      Grato,
       
      Cesar
       
    • Por violin101
      Caros amigos do grupo, saudações e um feliz 2025.
       
      Estou com uma pequena dúvida referente a Teclas de Atalho.

      Quando o Caps Lock está ativado o Comando da Tecla de Atalho não funciona.
      ou seja:
      se estiver para letra minúscula ====> funciona
      se estiver para letra maiúscula ====> não funciona
       
      Como consigo evitar essa falha, tanto para Letra Maiúscula quanto Minúscula ?

      o Código está assim:
      document.addEventListener( 'keydown', evt => { if (!evt.ctrlKey || evt.key !== 'r' ) return;// Não é Ctrl+r, portanto interrompemos o script evt.preventDefault(); });  
      Grato,
       
      Cesar
    • Por violin101
      Caros amigos, saudações.
       
      Por favor, poderiam me ajudar.

      Estou com a seguinte dúvida:
      --> como faço para para implementar o input código do produto, para quando o usuário digitar o ID o sistema espera de 1s a 2s, sem ter que pressionar a tecla ENTER.

      exemplo:
      código   ----   descrição
           1       -----   produto_A
       
      Grato,
       
      Cesar
×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.