Um Tutorial para a Linguagem de Programação Go – parte 7

Posted by ALOVasconcelos on Dec 2, 2009 in Dicas, Tutoriais, laboratório |

Ordenação

[Voltar ao menu]

Interfaces provêm uma forma simples de polimorfismo. Elas separam completamente
a definição do que um objeto faz de como ele o faz, permitindo implementações
distintas serem representadas em tempos diferentes pela mesma variável interface.

Como um exemplo, considere este exemplo simples de algoritmo de ordenação, tomado
do arquivo progs/sort.go:

    func Sort(data Interface) {
        for i := 1; i < data.Len(); i++ {
            for j := i; j > 0 && data.Less(j, j-1); j-- {
                data.Swap(j, j-1);
            }
        }
    }

O código precisa apenas de três métodos, os quais empacotamos na interface de ordenação:

    type Interface interface {
        Len() int;
        Less(i, j int) bool;
        Swap(i, j int);
    }

Podemos aplicar Sort a qualquer tipo que implemente Len, Less e Swap. A package sort inclui os métodos necessários para permitir ordenação de arrays de inteiros, strings, etc.; aqui está o código para arrays de int:

    type IntArray []int

 func (p IntArray) Len() int { return len(p); }
 func (p IntArray) Less(i, j int) bool { return p[i] < p[j]; }
 func (p IntArray) Swap(i, j int) { p[i], p[j] = p[j], p[i]; }

Aqui vemos métodos definidos para tipos que não sejam structs. Você pode definir métodos para qualquer tipo que defina e nomeie em sua package.

E agora uma rotina para testar, de progs/sortmain.go. Esta rotina utiliza uma função na package sort, omitida aqui em favor da brevidade, para testar se o resultado está ordenado.

    func ints() {
        data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586};
        a := sort.IntArray(data);
        sort.Sort(a);
        if !sort.IsSorted(a) {
            panic()
        }
    }

Se temos um novo tipo, o qual desejamos que seja capaz de ser ordenado, tudo do que precisamos é fazê-lo implementar os três métodos para aquele tipo, dessa forma:

    type day struct {
        num        int;
        shortName  string;
        longName   string;
    }

 type dayArray struct { 37 data []*day; 38 }

 func (p *dayArray) Len() int { return len(p.data); }
 func (p *dayArray) Less(i, j int) bool { return p.data[i].num < p.data[j].num; }
 func (p *dayArray) Swap(i, j int) { p.data[i], p.data[j] = p.data[j], p.data[i]; }

Impressão

[Voltar ao menu]

Os exemplos de impressão formatada vistos até então foram bem modestos. Nesta seção
falaremos sobre como formatação de I/O (entrada e saída) pode ser bem implementada em
Go.

Vimos simples usos da package fmt, que implementa Printf, Fprintf, e assim por diante.
Dentro da package fmt, Printf é declarado com a seguinte assinatura:

	Printf(format string, v ...) (n int, errno os.Error)

Aquelas reticências (...) representam uma lista variável de argumentos, que em C podem ser manipulados usando-se as macros de stdarg.h, mas em Go é passada usando uma variável de interface vazia (interface {}) e então desempacotada usando a biblioteca de reflexão. É off-topic (fora do assunto), mas o uso de reflexão ajuda a explicar algumas das belas propriedades do métodos Printf de Go, devido à habilidade do Printf de descobrir o tipo de seus argumentos dinamicamente.

Por exemplo, em C cada formato deve corresponder ao tipo de seu argumento. É mais fácil em muitos casos em Go. Ao invés de "%llud" você pode dizer simplesmente "%d"; Printf "sabe" o tamanho e sinal do inteiro e pode fazer a coisa certa pra você. O trecho de código

        var u64 uint64 = 1<<64-1;
        fmt.Printf("%d %d\n", u64, int64(u64));

imprime

	18446744073709551615 -1

De fato, se você for preguiçoso, o formato "%w" imprimirá, em um estilo simples e apropriado, qualquer valor, mesmo um array ou estrutura. A saída de

        type T struct { a int; b string };
        t := T{77, "Sunset Strip"};
        a := []int{1, 2, 3, 4};
        fmt.Printf("%v %v %v\n", u64, t, a);

é

 18446744073709551615 {77 Sunset Strip} [1 2 3 4]

Você pode descartar toda a formatação se usar Print ou Println ao invés de Printf, Estas rotinas implementam formatação completamente automática. A função Print apenas imprime seus elemetos usando o equivalente de "%v"
enquanto Println insere espaços entre argumentos e adiciona uma quebra de linha. A saída de cada uma destas duas linhas é idêntica, àquela da chamada a Printf feita acima.

        fmt.Print(u64, " ", t, " ", a, "\n");
        fmt.Println(u64, t, a);

Se você tem seu próprio tipo que gostaria que fosse formatado por Printf ou Print, apenas lhe dê um método String() que retorne uma string. As rotinas de impressão, examinarão o valor para verificar o mesmo implementa o método (String) e caso o faça, irá utiliza-lo em lugar de outra formatação. Aqui está um exemplo simples.

    type testType struct { a int; b string }

 func (t *testType) String() string {
 return fmt.Sprint(t.a) + " " + t.b
 }

 func main() {
 t := &testType{77, "Sunset Strip"};
 fmt.Println(t)
 }

Uma vez que testType tem um método String, o formatador default para o tipo o utilizará para produzir a saída

    77 Sunset Strip

Observe que o método String chama Sprint (a variante óbvia de Go que retorna uma string) para poceder sua formatação; formatadores especiais podem usar a biblioteca fmt recursivamente.

Outro recurso de Printf é que o formato "%T" imprimirá a representação em string do tipo de um valor, que pode ser prático quando depurando código polimórfico.

É possível escrever formatos de impressão completamente customizados com flags e precisões e etc, mas isto foge um pouco do escopo, então deixaremos como um exercício de exploração.

Você pode perguntar, contudo, como Printf pode prever se um tipo implementa o método String. Na verdade o que ele faz é perguntar "se" o valor pode ser convertido para uma variável interface
que implementa o método.
Esquematicamente, dado um valor v, ele faz isto:

	type Stringer interface {
		String() string
	}

	s, ok := v.(Stringer);  // Test whether v implements "String()"
	if ok {
		result = s.String()
	} else {
		result = defaultOutput(v)
	}

O código usa um type assertion ("v.(Stringer)") para testar se o valor armazenado em v satisfaz a interface Stringer; se satisfaz, s se tornará uma variável de interface implementando o método e ok será verdadeiro. Nós usamos então a variável de interface para chamar o método. (O padrão ",ok" é a forma Go de testar o sucesso de operações como uma conversão de tipo, atualização de map, comunicações e assim por diante, apesar
disso, esta é a única vez em que aparece neste tutorial).
Se o valor não satisfaz à interface, "ok" será falso (false).

N.T. Mais informações sobre type assertion em Go para Programadores C++ - parte 2

Neste trecho de código o nome "Stringer" segue a convenção de adicionar "[e]r" às interfaces descrevendo simples conjuntos de métodos como este.
Um último detalhe. Para completar a coleção, além de Printf etc. e Sprintf etc., existe também Fprintf etc. Diferente de C, o primeiro argumento de FPrintf não é um arquivo. Ao invés disse, é uma variável do tipo io.Write, que é um tipo interface definido na biblioteca io:

	type Writer interface {
		Write(p []byte) (n int, err os.Error);
	}

(Esta interface é outro nome convencional, desta vez para Write; Existe também io.Reader, io.ReadWriter e assim por diante). Deste modo você pode chamar Fprintf para qualquer tipo que implemente um método Write() padrão,
não apenas para arquivos, mas também rede, channels, buffers, o que quer que você deseje.

Continua...

Abraços

Tags: , , ,

2 Comments

Leave a Reply

XHTML: You can use these tags:' <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Copyright © 2008-2010 ALOVasconcelos All rights reserved.
Desk Mess Mirrored v1.5.1 theme from BuyNowShop.com.