Haskell – Typeclass

Neste post vou comentar sobre a estrutura typeclass do Haskell. A minha principal referência é o capítulo 6 do Real World Haskell.

Typeclasses

Como em Haskell existem tipos parametrizados, podemos querer definir funções que aceitam parâmetros com esse tipo. Entretanto, provavelmente a implementação da função será diferente para cada tipo.

Assim, typeclass é uma estrutura para prover essa funcionalidade. A sintaxe básica é a seguinte:

class <Nome> <Tipos parametrizados> where
    <função 1> :: <Assinatura de tipos>
    <função 2> :: <Assinatura de tipos>

Apesar do nome, um typeclass é bem diferente de uma classe que estamos acostumados em linguagens orientadas à objetos.

Um caso onde typeclass é útil é na implementação do operador de igualdade. Suponha que temos dois tipos Booleano e CorRGB:

data CorRGB = Vermelho | Verde | Azul
     deriving (Show)

data Booleano = Falso | Verdadeiro
     deriving (Show)

Podemos definir uma typeclass contendo uma função de igualdade, que recebe dois parâmetros de um mesmo tipo parametrizado a, e retorna True se forem iguais ou False caso contrário.

class Compara a where
      igual :: a -> a -> Bool

Aí podemos definir uma implementação para cada tipo, usando uma estrutura chamada instance. A sintaxe é semelhante ao typeclass, mas agora especificamos os tipos parametrizados e definimos a implementação da função. Para o caso do Booleano e da CorRGB, temos:

instance Compara Boolean where
         igual Verdadeiro Verdadeiro = True
         igual Falso Falso = True
         igual _ _  = False

instance Compara RGBColor where
         igual Vermelho Vermelho = True
         igual Verde Verde = True
         igual Azul Azul = True
         igual _ _ = False

Também podemos prover implementações padrões para um typeclass, definindo a implementação na estrutura class. Na execução, a implementação utilizada é sempre a mais específica.

Complementando o exemplo da typeclass Compara, podemos definir a função diferente e definir implementações padrões para ela e também para a função igual:

class Compara a where
      igual :: a -> a -> Bool
      igual x y =  not (diferente x y)

      diferente :: a -> a -> Bool
      diferente x y = not (igual x y)

Derivação automática

Na definição de Booleano e CorRGB, usamos a diretiva deriving(Show). Essencialmente Show é um typeclass da biblioteca padrão. A função show desse typeclass transforma um tipo genérico em String.

Fazendo alguns testes no terminal (ghci):

> show(Vermelho)
"Vermelho"
> :type show
show :: Show a => a -> String

Note que o comando :type usa uma sintaxe diferente para dizer que a função show pertence ao typeclass Show.

Podemos escrever nossa própria implementação para CorRGB ao invés de usar o padrão. Para isso, removemos a declaração deriving(Show) e adicionamos:

instance Show CorRGB where
         show Vermelho = "Red"
         show _ = "Not implemented"

Segundo [2], a funcionalidade do derive só pode ser usada com um conjunto específico de built-in typeclasses.

Typeclass com lista de tipos paramétricos

Imagine que queiramos implementar a função igual para uma lista de Booleanos. A primeira tentativa seria:

instance Compara [Boolean] where
         igual = undefined

No código acima, undefined é um tipo coringa que não causa erros de compilação devido a incompatibilidade de tipos, mas que causa um erro em tempo de execução. Essa técnica de usar undefined‘s é interessante para ir compilando versões intermediárias do seu código mesmo sem ter a implementação de todas as funções.

Enfim, veremos que tal trecho de código leva ao seguinte erro de compilação:

   Illegal instance declaration for `Compara [Boolean]'
      (All instance types must be of the form (T a1 ... an)
       where a1 ... an are *distinct type variables*,
       and each type variable appears at most once in the instance head.
       Use -XFlexibleInstances if you want to disable this.)
    In the instance declaration for `Compara [Boolean]'
Failed, modules loaded: none.

Há diversas maneiras de se contornar esse erro. Um deles é definir uma função exclusiva para listas, no caso da função igual, podemos criar uma igualLista,

class Compara a where
  ...
  igualLista :: [a] -> [a] -> Bool
  igualLista (xa:a) (xb:b) | diferente xa xb = False
                           | otherwise = igualLista a b
  igualLista [ ] [ ] = True
  igualLista _ _ = False
  ...

A vantagem nesse caso é que a implementação padrão deverá servir para a maior parte dos casos, pois em geral o teste de igualdade entre duas listas consiste em verificar se todos os elementos em ambas são iguais.

No caso de você querer definir a implementação apenas para uma lista de um tipo particular (p. e. [Booleano]), pode-se encapsular essa lista em uma estrutura chamada newtype.

O newtype é parecido com um tipo de dado algébrico (keyword data), com algumas diferenças como por exemplo o newtype exigir exatamente um data constructor e o tipo de um newtype é resolvido em tempo de compilação, ao contrário do data, e por isso seu uso não ocasiona overhead na execução do programa [1].

Assim, poderíamos fazer:

newtype Wrapper = Wrapper [Booleano]

Agora podemos implementar a função, com o inconveniente de encapsular os dados do novo tipo com Wrapper:

instance Compara Wrapper where
  igual (Wrapper x) (Wrapper y) = igualAux x y
    where igualAux (xa:a) (xb:b) | diferente xa xb = False
                                 | otherwise = igualAux a b
          igualAux [ ] [ ] = True
          igualAux _ _ = False

Uma boa referência para essas e outras alternativas é o haskell wiki [3].

Conclusão

Achei esse assunto bem complicado! Pra piorar, parece que o livro Real World Haskell no qual estou me baseando, escreveu muito mal os capítulos 5 e 6, dando exemplos muito complexos (talvez seja a diretriz do “real world”, mas definitivamente isso não é didático), confusos e chatos. Há diversos comentários que compartilham dessa opinião.

Apesar de tudo, continuarei meus estudos com esse livro, mesmo que eu só use os tópicos para me guiar e procurar explicações em referências como [2] e [3].

Referências

[1] http://book.realworldhaskell.org/read/using-typeclasses.html
[2] http://en.wikibooks.org/wiki/Haskell/Class_declarations
[3] http://www.haskell.org/haskellwiki/List_instance

Uma resposta a Haskell – Typeclass

  1. […] written about typeclasses in an old post (in Portuguese). Haskell defines a typeclass called […]

%d bloggers like this: