Polimorfismo, polymorphism, πολυμορφισμού, …

Polimorfismo, já refletiu sobre ele? (no sentido de pensar, não é necessário usar Reflector ou algo parecido). Certamente, a linguagem de programação que você usa, possui uma ou mais formas de manifestação deste recurso.

A definição da palavra polimorfismo conecta com a palavra pleomorfismo, um termo usado na Biologia – isto significa: “a ocorrência de duas ou mais forma no ciclo de vida de um organismo”.

Se assim como eu, você deseja simplificar sua vida, assuma que as palavras poly + morph, ambas de origem grega (polýs + morphos), podem representar muitas + forma. Quando contextualizadas pode significar a capacidade de alguma coisa (função, tipo de dado, instância, …) assumir muitas formas.

A minha idéia não é definir formalmente o que é polimorfismo (mesmo porque este assunto poderia ser polêmico, e eu não estou muito afim de discussão conceitual). Mas, a idéia é mostrar livremente algumas manifestações do “muitas formas” no contexto aplicado a Programação de Software, mesmo se tal recurso não for formalmente categorizado como polimorfismo.

Uma das manifestações do polimorfismo é através de Dynamic Dispatching, onde existe um mecanismo de passagem de mensagem resolvido em tempo de execução. Quando isto ocorre, existe uma espécie de contrato entre o caller e o callee. Neste caso, é esperado um certo número de argumentos e retorno compartilhados por um nome. Alguns exemplos deste tipo de cenário, seria os serviços do Windows Communication Foundation (WCF) ou o modelo de atores do Erlang – em linhas gerais, eles recebem uma requisição (request) através de um nome roteado internamente (endpoint no WCF, identifier num receiver no Erlang) e respondem (response ou reply) ou ignoram tal chamada.

Sobre dynamic dispatching, não posso deixar de citar uma das implementações mais populares atualmente: Objective-C. A passagem de mensagem ocorre através da notação [receiver message], message contempla o método (selector) a ser chamado e seus argumentos (actual parameters). Embora o C exerceu forte influência sobre o Objective-C, foi do Smalltalk que ele baseou suas extensões de Orientação a Objetos e o mecanismo de passagem de mensagem.

clip_image002

No exemplo apresentado, isto está materializado na chamada da função callConvert. Note que, ao contrário do C++, C# ou Java, as instâncias chamadas não possuem vinculos de herança, exceto pelo NSObject que é obrigatório para suportar o message passing (objc_msgSend). Então desde que uma instância suporte tal contrato, ela poderá ser chamada e responderá com sucesso, senão responderá com falha e retornará um valor default (note o comportamento das chamadas no painel inferior direito da figura). Isto não combina com Duck Typing? (por enquanto, vamos deixar o pato de lado…)

#import <Foundation/Foundation.h>
//Sample provided by Fabio Galuppo
const float PI = 3.14159265f;

@interface Radians2Degree : NSObject
{
}

-(float) Convert:(float)value;
@end

@implementation Radians2Degree
-(float) Convert:(float)value
{
    return 180 * (value / PI);
}
@end

@interface Degree2Radians : NSObject
{
}

-(float) Convert:(float)value;
@end

@implementation Degree2Radians
-(float) Convert:(float)value
{
    return PI * (value / 180);
}
@end

float callConvert(id receiver, float value)
{
    return [receiver Convert:value];
}

int main(int argc, const char* argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];    

    Radians2Degree* r2d = [[Radians2Degree alloc] init];
    Degree2Radians* d2r = [[Degree2Radians alloc] init];

    NSLog(@"2PI in degrees == %f\n", [r2d Convert:(2 * PI)]);
    NSLog(@"360 in radians == %f\n", [d2r Convert:(360)]);
    NSLog(@"PI in degrees == %f\n", [r2d Convert:([d2r Convert:(180)])]);
    NSLog(@"180 in radians == %f\n", [d2r Convert:([r2d Convert:(PI)])]);

    NSLog(@"(1/2)PI in degrees == %f\n", callConvert(r2d, PI / 2));
    NSLog(@"90 in radians == %f\n", callConvert(d2r, 90));

    NSLog(@"270 in radians == %f\n", callConvert(nil, 270));

    [pool release];

    return 0;
}

Dado uma classe X e outra Y que não se correspondem diretamente, seria possível encontrar dynamic dispatching em outras linguagens? Certamente! A linguagem C# (na verdade a Common Language Runtime – CLR) suporta Invoke via reflection desde a versão 1.0. Porém, usar reflection para isto é muito chato e irritante. No entanto, para melhor suportar este mecanismo, o C# 3.0 introduziu a palavra chave dynamic. O compilador + CLR fazem “a mágica” acontecer, veja:

using System;

//Sample provided by Fabio Galuppo

class Program
{
    sealed class X
    {
        public String M(String value){ return value.ToUpper(); }
    }

    sealed class Y
    {
        public String M(String value)
        {
            char[] temp = value.ToCharArray();
            Array.Reverse(temp);
            return new String(temp);
        }
    }

    sealed class Z : System.Dynamic.DynamicObject
    {
        Func<String, String> DefaultResult_ = s => String.Empty;

        public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
        {
            result = DefaultResult_;
            return true;
        }
    }

    static String Selector(dynamic id, String value)
    {
        String result = String.Empty;

        try
        {
            result = id.M(value);
        }
        catch(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException){}

        return result;
    }

    static void Main(string[] args)
    {
        Action<dynamic, String> m = (id, value) => Console.WriteLine("{0} -> {1}", value, Selector(id, value));

        m(new X(), "Hello");
        m(new Y(), "World");
        m(new object(), "Something");
        m(new Z(), "Something");

        Console.ReadLine();
    }
}

O método estático Selector invoca dinamicamente um método chamado M que recebe uma String cujo responde uma String (em destaque o contrato da operação). O comportamento é exatamente igual ao mecanismo de passagem de mensagem do Objective-C! Exceto que no Objective-C, o resultado retornado para uma instância cujo não possui um contrato compatível é o valor default da operação. Já no C# ocorre uma exceção pomposa: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException. Se desejar ter o mesmo comportamento do Objective-C (retornar o valor default), é necessário criar um interceptador, como a classe Z do exemplo.

Uma reflexão sobre notação:

Objective-C: [receiver Convert: value To: RADIANS];

C#: receiver.Convert (value, to = RADIANS);

Assim como as derivadas possuem suas diversas notações (da/dx – Leibniz, å – Newton) , o mecanismo de passagem de mensagem também possui tal notação baseado no gosto e expressão de seus criadores (Brad Cox, Anders Hejlsberg).

Continuando, tudo isto soou como Late Binding, não é mesmo? E late binding nos remete aos anais do Component Object Model (COM). O que seria do COM sem ele? (talvez a não existência de programadores Visual Basic, pensando bem…, deixa para lá… Hot smile brincadeira – Visual Basic é uma nobre linguagem de programação que sofre preconceito de programadores elitistas, porém se usada pelas pessoas certas, podem fazer coisas muito legais) .

Com toda esta gloriosa volta do COM (WinRT, o novo COM), seria um bom momento para revisitá-lo. Focando no late binding com Active Template Library (ATL) e C++:

clip_image004

COM IS LOVE by Developmentor in their golden years (Don Box and team)

Eu ainda tenho a camiseta que eles distribuiam nos eventos. Surprised smile

O COM tem seu mecanismo de dynamic dispatching implementado em termos da interface IDispatch. ATL ajuda muito, tanto na construção do componente suportando late binding, bem como no seu consumo. Tudo relacionado aos componentes COM é dependente de interfaces, e estas por sua vez são descritas no código fonte (C++, por exemplo) e na interface definition language (IDL). Abaixo temos os melhores momentos da construção de 2 componentes COM suportando IDispatch (XType e YType):

class ATL_NO_VTABLE CXType :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<CXType, &CLSID_XType>,
    public IDispatchImpl<IXType, &IID_IXType, &LIBID_InvokeMeLib, 1, 0>
{
public:
…

BEGIN_COM_MAP(CXType)
    COM_INTERFACE_ENTRY(IXType)
    COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

…

public:
    STDMETHOD(M)(BSTR value, BSTR* result);
};

#include "XType.h"

STDMETHODIMP CXType::M(BSTR value, BSTR* result)
{
    CComBSTR c(value);

    HRESULT hr = c.ToUpper();
    if(SUCCEEDED(hr))
    {
        *result = c;
        return S_OK;
    }

    return hr;
}

class ATL_NO_VTABLE CYType :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<CYType, &CLSID_YType>,
    public IDispatchImpl<IYType, &IID_IYType, &LIBID_InvokeMeLib, 1, 0>
{
public:
…

BEGIN_COM_MAP(CYType)
    COM_INTERFACE_ENTRY(IYType)
    COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

…

public:
    STDMETHOD(M)(BSTR value, BSTR* result);
};

#include "YType.h"

STDMETHODIMP CYType::M(BSTR value, BSTR* result)
{
    CComBSTR c(value);

    USES_CONVERSION;

    char* x = OLE2A(c);
    strrev(x);
    *result = CComBSTR(x);

    return S_OK;
}

As classes CXType e CYType do exemplo ATL/COM/C++ imitam as classes X e Y, respectivamente, implementadas no C#. Então, você leitor conhecedor do C# (se não conhece, recomendo fortemente este livro), poderá mapear tais recursos e tirar suas conclusões.

O consumo do componente e a chamada late binding feita através de Invoke (via Selector) são mostradas na figura abaixo:

clip_image006

No exemplo, está em destaque o comportamento de uma chamada com contrato não suportado, onde a resposta (HRESULT) é Unknown name. O CComPtr<IDispatch> é uma especialização (usando o vocabulário do C++, isto quer dizer um template specialization) que ajuda MUITO as chamadas via Invoke (e outros).

#import "..\\..\\InvokeMe\\Debug\\InvokeMe.dll" raw_interfaces_only named_guids no_namespace

#include <atlbase.h>

#include <iostream>

//Sample provided by Fabio Galuppo

HRESULT Selector(CComPtr<IDispatch>& id, const CComBSTR& value, CComBSTR& result)
{
    CComVariant vValue(value), vResult;
    HRESULT hr = id.Invoke1(OLESTR("M"), &vValue, &vResult);
    if(SUCCEEDED(hr)) result = vResult.bstrVal;
    return hr;
}

int main(int argc, char* argv[])
{
    CoInitialize(NULL);
    {
    HRESULT hr = E_FAIL;

    CComPtr<IDispatch> x, y;    

    auto m = [](CComPtr<IDispatch>& id, const CComBSTR& value)
    {
        CComBSTR result;
        USES_CONVERSION;
        HRESULT hr = Selector(id, value, result);
        std::cout << OLE2CA(value) << " -> " << (SUCCEEDED(hr) ? OLE2CA(result) : "") << std::endl;
    };

    hr = x.CoCreateInstance(CLSID_XType, NULL, CLSCTX_ALL);
    if(SUCCEEDED(hr))
        m(x, CComBSTR("Hello"));

    hr = y.CoCreateInstance(CLSID_YType, NULL, CLSCTX_ALL);
    if(SUCCEEDED(hr))
        m(y, CComBSTR("World"));

    CComPtr<IDispatch> z;
    z.CoCreateInstance(OLESTR("Shell.Explorer"), NULL, CLSCTX_ALL);
    if(SUCCEEDED(hr))
        m(z, CComBSTR("Something"));
    }
    CoUninitialize();

    std::cin.get();
}

Compare o método Selector do C# com a função Selector do C++, elas fazem a mesma tarefa: invocam dinamicamente um método (função ou mensagem, dependendo da nomenclatura que deseja usar) chamado “M”.

Interessante isto, não é mesmo? Você notou que para um determinado recurso apareceu diversas notações, nomenclaturas e conceitos? Parece que é para ferrar (para não qualificar com outro adjetivo) com a cabeça do programador.

Dynamic dispatching é um recurso que pode ser muito custoso do ponto de vista de desempenho. Então, antes de usar e abusar, se atente aos requisitos não-funcionais do seu software.

Dentro do universo polimorfismo, existiria alguma coisa nesta linha (de comportamento semelhante) cujo a resolução ocorresse em tempo de compilação e tivesse as garantias da tipagem estática? Com absoluta certeza! Posso garantir que isto existe e formalmente é denominado de Polimorfismo Paramétrico. Isto é refletido diretamente num recurso do C++ chamado templates (e não confunda isto com generics, cujo possuem uma intercecção).

Vamos criar um nome paradoxal para esta brincadeira e chamar tal recurso de static duck typing (olha o pato aparecendo novamente).

Abaixo, uma implementação que remete ao comportamento dos exemplos anteriores com templates e C++:

clip_image008

Internamente, as funções selector e m, serão recriadas pelo compilador, baseado na quantidade de template instantiation.

Ocultamente, o F# também suporta este tal de “static duck typing”. Isto é feito através de inline functions. Estas funções carregam na sua assinatura um contrato formal, por exemplo, (member M : String -> String):

clip_image010

clip_image012

Apesar de ter o mesmo comportamento do C++ template, replicando código com o tipo adequado durante a compilação, você notará 2 coisas que as funções inline fazem:

1. O que um inline supostamente deveria fazer. Substituir a chamada pelas respectivas linhas de código no contexto chamador (linhas L_002f – L_0052).

clip_image014

2. Conserva a estrutura da função inline, mas se chamada dinamicamente gerará uma exceção.

clip_image016

Agora o último ato, o popular, le magnifique, o inconfundível: polimorfismo baseado em hierarquia de classes. Ele mesmo! Aquele que conhecemos através de classes, métodos virtuais e interfaces das linguagens C++, Java, C#, entre outros. Formalmente, este tipo de polimorfismo é denominado de Subtyping Polymorphism.

Aqui vamos explorar apenas 1 modelo: o subtyping polymorphism com dynamic dispatching. Para isto ser possível, em C++, o recurso usado são membros do tipo função virtual. Imagine a seguinte hierarquia e seu consumo:

#include <cstdio>

const float PI = 3.14159265f;

class IConverter
{
public:
    virtual float Convert(float value) = 0;
};

class Radians2Degree : public IConverter
{
public:
    virtual float Convert(float value)
    {
        return 180 * (value / PI);
    }
};

class Degree2Radians : public IConverter
{
public:
    virtual float Convert(float value)
    {
        return PI * (value / 180);
    }
};

int main()
{
    Radians2Degree r2d;
    Degree2Radians d2r;

    IConverter* converter = &r2d;
    std::printf( "2PI in degrees == %f\n", converter->Convert(2 * PI) );

    converter = &d2r;
    std::printf( "360 in radians == %f\n", converter->Convert(360.f) );

    return 0;
}

Você notará que existe uma classe virtual pura (IConverter) servindo de interface base para outras classes. Outros exemplos de tal técnica com interfaces são IUnknown e IInspectable, do COM e da WinRT, respectivamente. Quando existir um membro virtual, existirá a famosa vtable.

A vtable é o mecanismo que possibilita o dynamic dispatching. Sendo que, seu funcionamento é bem simples: imagine um vetor de ponteiros de funções, cujo existe um outro ponteiro (vtable pointer) que referencia esta tabela de funções como membro “privado” ou interno da instância chamadora. Veja este relacionamento na figura abaixo. Toda esta indireção ocorre através do vtable pointer, onde um objeto aciona o ponteiro de função que deseja invocar através de um indice:

clip_image018

No detalhe, em assembly x64, os registradores RCX e RAX mantêem os endereços do objeto (seu this pointer) e do endereço do endereço da função a ser chamada (Convert – 0x000000013F4E68A0). O resultado apresentados estão na forma Little Endian:

clip_image020

Um resumo das principais operações assembly x64 são:

000000013F4E1079  lea         rax,[r2d]
000000013F4E107E  mov         qword ptr [converter],rax

000000013F4E1093  mov         rax,qword ptr [converter]
000000013F4E1098  mov         rax,qword ptr [rax]

000000013F4E109E  mov         rcx,qword ptr [converter]
000000013F4E10A3  call        qword ptr [rax]  <= breakpoint

RAX    = 0x000000013F4E68A0
RCX    = 0x00000000002CFB38
Memory = 0x000000013F4E68A0  14 10 4e 3f 01 00 00 00 … = littleEndianOf(0x000000013f4e1014)

XMM0 = 00000000000000000000000043B40000 =  resultado final

Apenas por curiosidade, vamos registrar o disassembly do método Convert de Radians2Degree.

clip_image022

A primeira coisa a ser verificada é que para o assembly não existe um método. Há apenas um endereço 64-bit, que poderá ser invocado através do opcode call, cujo a stack indica a presença de 2 paramêtros formais: o this pointer e um float denominado value. Os 2 primeiros passos são movimentos dos registradores onde os parametros foram armazenados antes da chamada para áreas de memória referentes a seus argumentos, o resto das operações são pertinentes aos cálculos. O “S” no final das instruções assembly significa Single, ou seja, operações com float (32-bit ou 4 bytes). Por final, a cópia do resultado vai para o registrador SSE XMM0. Ou seja, simples, muito simples, até “um cavalo entende”. Winking smile

Dentro do universo amplo do polimorfismo, gostaria de destacar mais 2 modelos:

1. Ad-hoc polymorphism – um nome bonito para sobrecarga de métodos, funções ou operadores.

2. Co-variância e Contra-variância – Conversão do mais específico para o mais genérico e conversão do mais genérico para o mais especifico, respectivamente.

Mas estes são assuntos para uma outra oportunidade, ou para alguma palestra, ou treinamento que eu venha a ser contratado para ministrar.

Samples Polymorphism Source Code

About Fabio Galuppo

I'm Software Engineer and Professional Trainer. I love Programming and Rock'n'Roll.
This entry was posted in C++, Computer Science, F#. Bookmark the permalink.

1 Response to Polimorfismo, polymorphism, πολυμορφισμού, …

  1. Icaro Camelo says:

    Parabéns pelo artigo! Muito bem explicado!

Leave a comment