• Nenhum resultado encontrado

Como se define um tipo algébrico?

No documento Programação Funcional Usando Haskell (páginas 142-145)

5.3 Tipos algébricos

5.3.1 Como se define um tipo algébrico?

Um tipo algébrico é mais uma facilidade que Haskell oferece, proporcionando ao programador maior poder de abstração, necessário para modelar estruturas de dados complexas. Outras possibilidades também existem, como por exemplo, os tipos abstratos de dados a serem vistos mais adiante. A sintaxe da declaração de um tipo algébrico de dados é

data <Const_tipo> = <Const_Val1> | <Const_Val2> | ... | <Const_Valn>

onde Const_tipo é o “construtor do tipos” e Const_Val1, Const_Val2, ... Const_Valn são os “elementos” ou “valores do tipo”. Cada valor do tipo é composto de um “construtor de valor”, também conhecido por “construtor de dado”, seguido, ou não, de componentes do valor. Vamos mostrar um exemplo caracterizando cada um destes elementos.

data Publicacao = Livro Int String [String] | Revista String Int Int | Artigo String String Int

deriving (Show)

Neste caso, Publicacao é o construtor do tipo e Livro, Revista e Artigo são os construtores dos valores ou dos dados. Os tipos Int, String e [String] são os componentes do valor Livro. De forma similar, String, Int e Int são os componentes do valor Revista e String, String e Int são os componentes do valor Artigo. A declaração deriving (Show) que complementa esta declaração de tipo informa que este tipo de dado deve pertencer a classe Show.

Os componentes dos valores exercem os mesmos papeis que os campos dos registros nas linguagens de programação tradicionais. O componente Int seguindo o construtor do valor Livro é reservado para ser uma chave que identifica o livro em uma biblioteca ou em um Banco de Dados. O componente String deve ser utilizado para identificar o nome do livro e a lista [String] é reservada para representar os autores do livro.

Agora é possível criar uma instância de uma Publicacao da seguinte forma:

pub = Livro 9780596514983 "Real World Haskell"

["Bryan O’Sullivan", "John Goerzen", "Don Stewart"]

Em ghci, o tipo do construtor Livro pode ser mostrado da seguinte forma: <ghci>: type Livro

Livro :: Int -> String -> [String] -> Publicacao

Isto significa que os construtures de valores podem ser tratados como funções que criam e retornam uma nova publicação.

O tipo enumeração

O tipo enumeração, (enum), implementado nas linguagens C e C++ mantém alguma semelhança com os tipos algébricos implementados em Haskell, apesar de ter alguma diferença. Nas linguagens C e C++, os valores dos tipos enumeração são valores inteiros, podendo ser

utilizados em qualquer contexto onde um valor inteiro seja esperado, representando uma fonte de possív eis bugs indesejáveis. Este não é o caso dos tipos algébricos em Haskell. Por exemplo, em C, o tipo enumeração dia_util pode ser definido da seguinte forma:

enum dia_util {segunda, terca, quarta, quinta, sexta};

Este tipo pode ser definido, em Haskell, como um tipo algébrico, com a vantagem da ausência dos bugs citados. Senão vejamos:

data Dia_util = Segunda | Terca | Quarta | Quinta | Sexta

deriving (Eq, Show) O tipo união livre

A união livre é um tipo de dado implementado em C e C++ que pode ser usado para modelar várias alternativas. Em C e C++, não é informada qual alternativa está presente. Isto significa que a união livre representa uma fonte de erros de programação porque o type checker não pode fica impossibilitado de realizar seu trabalho. Seja, por exemplo, as declarações a seguir: enum forma {circulo, retangulo};

struct ponto {float x, y}; struct circulo {

struct ponto centro; float raio;

};

struct retangulo { struct ponto canto; float base;

float altura; };

struct figura { enum forma tipo; union {

struct circulo meucirculo; struct retangulo meuretangulo; }fig_gemetrica;

};

Neste exemplo, a união pode conter dados válidos para uma figura do tipo circulo ou para uma figura do tipo retangulo. Temos de usar o campo tipo do tipo enum forma para indicar explicitamente que tipo de valor está atualmente armazenado na união. No entanto, o campo tipo pode ser atualizado sem que a união também seja, ou então a uníão pode ser atualizada sem que o campo tipo também seja. Isto implica em insegurança na linguagem porque um valor que seja um círculo pode ser usado como se fosse um retângulo e vice-versa. A versão em Haskell para este código é dramaticamente menor e segura. Senão vejamos:

data Ponto = (Float, Float)

data Figura = Circulo Ponto Float

| Retangulo Ponto Float Float

Se for criado um valor do tipo Figura usando o construtor de valores Circulo, não é possível utilizá-lo, acidentalmente, como um valor construído pelo construtor de valores Retangulo.

Produtos de tipos

Já foi visto que um novo tipo de dado é criado utilizando a declaração data. Haskell também permite a criação de sinônimos para dar um significado mais claro a algum tipo, utilizando para isso a declaração type. Também é sabido que um produto de tipos é um novo tipo de dados, cujos valores são construídos com mais de um construtor. Por exemplo,

type Nome = String type Idade = Int

data Gente = Pessoa Nome Idade

A leitura de um valor do tipo Gente deve ser feita da seguinte forma: para construir um valor do tipo Gente, é necessário suprir um construtor de valor, Pessoa, juntamente com dois campos, digamos, n do tipo Nome e outro, digamos i, do tipo Idade. O elemento formado será Pessoa n i. O construtor Pessoa funciona como uma função aplicada aos outros dois objetos, n e i. Exemplos de valores deste tipo podem ser:

Pessoa "Barack Obama" 40 Pessoa "Mahatma Ghandi" 71

Podemos formar uma função mostraPessoa que toma um valor do tipo Gente e o mostra na tela:

mostraPessoa :: Gente -> String

mostraPessoa (Pessoa n a) = n ++ " -- " ++ show a

A aplicação da função mostraPessoa (Pessoa "John Lennon", 50) será respondida por >"John Lennon -- 50"

Neste caso, o tipo Gente tem o único construtor, Pessoa, que utiliza dois valores dos tipos Nome e Idade, para formar um valor do tipo Gente. O valor Pessoa n a pode ser interpretado como sendo o resultado da aplicação de uma função, Pessoa, aos argumentos n e a, ou seja, Pessoa :: Nome − > Idade − > Gente.

O tipo para Gente poderia também ser definido como uma tupla, ou seja, como o tipo type Gente = (Nome, Idade)

Existem vantagens e desvantagens nesta versão, no entanto, é senso comum que ela deve ser evitada. Os motivos são baseados na legibilidade, porque valores construídos desta forma nada indicam sobre a relação mnemônica que deve existir entre um tipo e seus valores.

Finalmente, ao se introduzir um tipo algébrico, podemos desejar que ele faça parte de de- terminadas classes de tipos. Esta função é provida pela declaração deriving mostrada nas declarações anteriores.

1. Sendo a Estação = {Primavera, Verão, Outono, Inverno} e Tempo = {Quente, Frio}, defina a função tempo :: Estacao − > Tempo de forma a usar guardas em vez de pattern matching.

2. Defina o tipo Meses como um tipo algébrico em Haskell. Faça uma função que associe um mês a sua Estação. Coloque ordenação sobre o tipo.

3. Defina uma função que calcule o tamanho do perímetro de uma forma geométrica do tipo Forma, definido no texto desta sub-seção.

4. Adicione um construtor extra ao tipo Forma para triângulos e estenda as funções area e perimetro (exercício anterior) para incluir os triângulos.

No documento Programação Funcional Usando Haskell (páginas 142-145)