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

Posted by ALOVasconcelos on Nov 30, 2009 in Dicas, Mensagens, Tutoriais, laboratório |

Gatos podres

[Voltar ao menu]

N.T. – O título original é um tipo de trocadilho com o nome do comando “cat” do Unix. A propósito, cat – o comando – vem de concatenate (concatenar). Além disso, será implementado aqui uma versão de cat que utiliza um algoritmo de criptografia chamado rot13.

Escrita na package file, aqui está uma versão simples do utilitário cat:

    package main

    import (
           "./file";
           "flag";
           "fmt";
           "os";
    ) 

    func cat(f *file.File) {
        const NBUF = 512;
        var buf [NBUF]byte;
        for {
            switch nr, er := f.Read(&buf); true {
            case nr < 0:
                fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", f.String(), er.String());
                os.Exit(1);
            case nr == 0:  // EOF
                return;
            case nr > 0:
                if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr {
                    fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", f.String(), ew.String());
                }
            }
        }
    }

    func main() {
        flag.Parse();   // Scans the arg list and sets up flags
        if flag.NArg() == 0 {
            cat(file.Stdin);
        }
        for i := 0; i < flag.NArg(); i++ {
            f, err := file.Open(flag.Arg(i), 0, 0);
            if f == nil {
                fmt.Fprintf(os.Stderr, "cat: can't open %s: error %s\n", flag.Arg(i), err);
                os.Exit(1);
            }
            cat(f);
            f.Close();
        }
    }

Por enquanto isto deve ser fácil de acompanhar, mas a declaração switch apresenta alguns novos recursos. Como um loop for, um if ou switch pode incluir uma declaração de inicialização. O switch na linha 18 usa uma (declaração) para criar as variáveis nr e er para armazenar o valor de retorno de f.Read(). (O if na linha 25 segue a mesma idéia). A declaração switch é geral: ela avalia os cases do cima para baixo, procurando pelo primeiro case que case (misericória!) com o valor; as expressões case não precisam ser constantes ou mesmo inteiros, desde que todos tenham o mesmo tipo.
Quando o valor do switch for verdadeiro, podemos deixá-lo vazio - como é também a situação em uma declaração for, um valor não informado significa true. De fato, o switch é uma forma de cadeia de if-else. Neste ponto, deve ser mencionado que em declarações switch cada case tem um break implícito.

A linha 25 chama Write(), fatiando (em Slices) o buffer de entrada, que é ele mesmo um Slice. Slices proporcionam a forma padão de manipular buffers de entrada e saída em Go.

Agora façamos uma variação de cat que opcionalmente criptografa sua entrada usando o algoritmo rot13. É fácil de fazer, simplesmente processando os bytes, mas ao invés disso exploraremos a noção de interface de Go.
A subrotina cat utiliza apenas dois métodos de f: Read() e String(), então comecemos definindo uma interface que em exatamente estes dois métodos. Aqui está o código do programa cat_rot13.go:

    type reader interface {
        Read(b []byte) (ret int, err os.Error);
        String() string;
    }

Qualquer tipo que tenha os dois métodos de reader - indiferente de quaisquer outros métodos que o tipo possa ter - implementa a interface. Uma vez que file.File implementa estes métodos, implementa a interface reader. Podemos forçar a sobrotina cat a aceitar um reader ao invés de um *file.File e funcionaria bem, mas vamos enfeitar um pouco, primeiro escrevendo um segundo tipo que implementa reader, um que encapsula um reader existente e criptografa os dados com rot13. Para fazer isto, nós apenas definimos o tipo e implementamos os métodos sem muito código adicional, temos uma segunda implementação da interface reader.

    type rotate13 struct {
        source    reader;
    }

 func newRotate13(source reader) *rotate13 {
     return &rotate13{source}
 }

 func (r13 *rotate13) Read(b []byte) (ret int, err os.Error) {
     r, e := r13.source.Read(b);
     for i := 0; i < r; i++ {
         b[i] = rot13(b[i])
     }
     return r, e
 }

 func (r13 *rotate13) String() string {
     return r13.source.String()
 }
 // fim de rot

(A função rot13, chamada na linha 42 é trivial e não vale a pena reproduzi-la aqui.)

Para usar o novo recurso, definimos um flag:

    var rot13Flag = flag.Bool("rot13", false, "rot13 the input")

e a usamos de dentro de uma quase inalterada função cat():

    func cat(r reader) {
        const NBUF = 512;
        var buf [NBUF]byte;

        if *rot13Flag {
             r = newRotate13(r)
        }
        for {
             switch nr, er := r.Read(&buf); {
             case nr < 0:
                 fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", r.String(), er.String());
                 os.Exit(1);
             case nr == 0: // EOF
                 return;
             case nr > 0:
                 nw, ew := file.Stdout.Write(buf[0:nr]);
                 if nw != nr {
                      fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", r.String(), ew.String());
                 }
             }
       }
 }

(Podemos também empacotar em main e deixar cat() sozinho, exceto pela alteração do tipo de argumento; considere isto como um exercício.) As linhas de 56 até a 58 definem tudo; se o flag rot13 for verdadeiro (true), empacota o reader que recebemos dentro de um rotate13 e prossegue. Observe que as variáveis da interface são valores, não ponteiros: o argumento é do tipo reader, não *reader, embora por trás ele contenha um ponteiro para um struct.

Aqui está ele em ação:

    % echo abcdefghijklmnopqrstuvwxyz | ./cat
    abcdefghijklmnopqrstuvwxyz
    % echo abcdefghijklmnopqrstuvwxyz | ./cat --rot13
    nopqrstuvwxyzabcdefghijklm
    %

Fãs de dependency injection podem animar-se com quão facilmente interfaces nos permitem substituir a implementação de um descritor de arquivo.

Interfaces são um recurso distinto de Go. Uma interface é implementada por um tipo se o tipo implementa todos os métodos declarados na interface. Isto significa que um tipo pode implementar um número arbitrário de diferentes interfaces. Não existe hierarquia de tipo; as coisas podem ser mais ad hoc, como vimos com rot13. O tipo file.File implementa reader; ele pode também implementar um writer, ou qualquer outra interface construída a partir de seus métodos que se encaixem na situação atual.
Considere a interface Empty:

type Empty interface {}

Todo tipo implementa a interface Empty, o que a faz útil para coisas como containers.

Continua...

Abraço

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.