Um Tutorial para a Linguagem de Programação Go – parte 7
Ordenação
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
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.
Abraços
[...] Ordenação [...]
[...] Continua… [...]