Playlists
atualiza_preco :: (Integer, String, Double) -> Double -> (Integer, String, Double)
atualiza_preco (id_prod, nome, preco) inflacao = (id_prod, nome, preco*(1 + inflacao))
Sem uma documentação, a assinatura dessa função não torna claro seu objetivo:
atualiza_preco :: (Integer, String, Double) -> Double -> (Integer, String, Double)
atualiza_preco (id_prod, nome, preco) inflacao = (id_prod, nome, preco*(1 + inflacao))
Descreve um produto pelo código, nome e preço?
Para piorar, podemos ter uma outra tupla do mesmo tipo descrevendo outro contexto:
troco :: (Integer, String, Double) -> (Integer, String, Double) -> Double
troco (id_prod, nome, preco) (id_cli, nome_cli, pago) = pago - preco
Agora o Double
da tupla representa valor pago.
troco :: (Integer, String, Double) -> (Integer, String, Double) -> Double
troco :: Produto -> Cliente -> Double
type
:
type Produto = (Integer, String, Double)
type Cliente = (Integer, String, Double)
pago (_, _, p) = p
preco = pago
troco :: Produto -> Cliente -> Double
troco produto cliente = pago cliente - preco produto
Note que type
cria apenas um apelido para um certo tipo,
ou seja, o seguinte código vai compilar:
leite = (10, "leite", 2.5)
lucas = (2391, "Lucas", 10)
troco lucas leite
Saída:
String
:
type String = [Char]
Podemos também criar tipos paramétricos, ou seja, com uma ou mais variáveis de tipo em aberto:
type Assoc k v = [(k,v)]
find :: Eq k => k -> Assoc k v -> v
find k t = head [v | (k',v) <- t, k == k']
find 2 [(1,3), (5,4), (2,3), (1,1)]
Saída:
type BinTree a = (BinTree a, a, BinTree a)
Tipo soma:
data Bool = True | False
data
: declara que é um novo tipoBool
: nome do tipoTrue | False
: poder assumir ou True ou FalseVamos criar um tipo que define a direção que quero andar:
data Dir = Norte | Sul | Leste | Oeste
Com isso podemos criar a função para
:
type Coord = (Int, Int)
type Passo = Coord -> Coord
data Dir = Norte | Sul | Leste | Oeste
para :: Dir -> Passo
para Norte (x,y) = (x, y + 1)
para Sul (x,y) = (x, y - 1)
para Leste (x,y) = (x + 1, y)
para Oeste (x,y) = (x - 1, y)
E a função caminhar
:
type Coord = (Int, Int)
type Passo = Coord -> Coord
data Dir = Norte | Sul | Leste | Oeste
caminhar :: [Dir] -> Passo
caminhar [] coord = coord
caminhar (d:ds) coord = caminhar ds (para d coord)
data Ponto = MkPonto Double Double
data
: declara que é um novo tipoPonto
: nome do tipoMkPonto
: construtor (ou envelope) - declaração implícita de uma
função usada para criar um valor do tipo Ponto
Double Double
: tipos que ele encapsulaO nome do tipo e o nome do construtor podem ser (e tipicamente são) os mesmos! Apesar de PODEREM ser os mesmos nomes, vivem em espaços de nomes diferentes e NÃO são a mesma coisa.
Para ser possível imprimir esse tipo:
data Ponto = MkPonto Double Double
deriving (Show)
deriving
: derivado de outra classe
Show
: tipo imprimível
Isso faz com que o Haskell crie automaticamente uma instância da
função show
para esse tipo de dado.
dist :: Ponto -> Ponto -> Double
dist (MkPonto x y) (MkPonto x' y') = sqrt $ (x-x')^2 + (y-y')^2
dist (MkPonto 1 2) (MkPonto 1 1)
Saída:
data Forma = Circulo Ponto Double
| Retangulo Ponto Double Double
-- um quadrado é um retângulo com os dois lados iguais
quadrado :: Ponto -> Double -> Forma
quadrado p n = Retangulo p n n
Circulo
e Retangulo
são funções construtoras:> :t Circulo
Circulo :: Ponto -> Double -> Forma
> :t Retangulo
Retangulo :: Ponto -> Double -> Double -> Forma
Maybe
:data Maybe a = Nothing | Just a
Maybe a
pode não ser nada ou
pode ser apenas o valor de um tipo a
.-- talvez a divisão retorne um Int
maybeDiv :: Int -> Int -> Maybe Int
maybeDiv _ 0 = Nothing
maybeDiv m n = Just (m `div` n)
maybeHead :: [a] -> Maybe a
maybeHead [] = Nothing
maybeHead xs = Just (head xs)
case
:divComErro :: Int -> Int -> Int
divComErro m n = case (maybeDiv m n) of
Nothing -> error "divisão por 0"
Just x -> x
Either
definido como:data Either a b = Left a | Right b
-- ou retorna uma String ou um Int
eitherDiv' :: Int -> Int -> Either String Int
eitherDiv' _ 0 = Left "divisao por 0"
eitherDiv' m n = Right (m `div` n)
print $ eitherDiv' 2 2
print $ eitherDiv' 2 0
Saída:
Crie um tipo Fuzzy
que pode ter os valores Verdadeiro
;
Falso
; ou Pertinencia Double
que define um intermediário entre
Verdadeiro
e Falso
.
Crie uma função fuzzifica
que recebe um Double
e retorna
Falso
caso o valor seja menor ou igual a 0, Verdadeiro
se for
maior ou igual a 1 e Pertinencia v
caso contrário.
data Fuzzy = Falso | Pertinencia Double | Verdadeiro
fuzzifica :: Double -> Fuzzy
fuzzifica x | x <= 0 = Falso
| x >= 1 = Verdadeiro
| otherwise = Pertinencia x
newtype
, que permite apenas um construtor:newtype Nat = N Int
A diferença entre newtype
e type
é que o primeiro define um
novo tipo enquanto o segundo é um sinônimo.
A diferença entre newtype
e data
é que o primeiro define um
novo tipo até ser compilado, depois ele é substituído como um
sinônimo. Isso ajuda a garantir a checagem de tipo em tempo de
compilação.
data Tree a = Leaf a | Node (Tree a) a (Tree a)
a
, ou é um
nó contendo uma árvore à esquerda, um valor do tipo a
no meio
e uma árvore à direita.t :: Tree Int
t = Node (Node (Leaf 1) 3 (Leaf 4)) 5
(Node (Leaf 6) 7 (Leaf 9))
Podemos definir uma função contem
que indica se um elemento x
está
contidado em uma árvore t
:
contem :: Eq a => Tree a -> a -> Bool
contem (Leaf y) x = x == y
contem (Node l y r) x = x == y || l `contem` x
|| r `contem` x
print $ t `contem` 5
print $ t `contem` 0
Saída:
contem
levando em conta que essa é uma árvore
de busca, ou seja, os nós da esquerda são menores ao nó atual, e
os nós da direita são maiores.contem :: Ord a => Tree a -> a -> Bool
contem (Leaf y) x = x == y
contem (Node l y r) x | x == y = True
| x < y = l `contem` x
| otherwise = r `contem` x
é com a sintaxe Record Type :
data Ponto3D = Ponto { coordX :: Double
, coordY :: Double
, coordZ :: Double
}
Essa sintaxe cria automaticamente as funções coordX, coordY, coordZ
que retornam o valor daquele campo do seu tipo.
data Zero
data Um = ()
Zero
é um tipo que não contém valor algum!Um
é um tipo contendo apenas um único valor!
data Zero
data Um = ()
Zero
,
pois não temos como passar algo que não existe:
data Zero
absurdo :: Zero -> a
absurdo ?? = ??
Um
e retorna um valor de um tipo específico,
é equivalente a declarar uma variável contendo tal valor:
data Um = ()
inteiro :: Um -> Int
inteiro () = 10
var_inteiro :: Int
var_inteiro = 10
a -> Um
que recebe
um valor de qualquer tipo, porém não traz informação alguma:
data Um = ()
fim :: a -> ()
fim x = ()
Zero
e Um
são definidos no Haskell como Void
e ()
(chamado unit ).Either
é representante da soma de tipos, e o tipo Pair
representa a operação
de produto:
data Either a b = Left a | Right b
data Pair a b = (a, b)
Either Void ()
pode ser lido como a soma de \(0\) e \(1\):
type ZeroMaisUm = Either Void ()
ZeroMaisUm
?type ZeroMaisUm = Either Void ()
Bool
:
data Bool = False | True
data Bool = False | True
type Bool' = Either () ()
Bool
em Bool'
e vice-versa:
data Bool = False | True
type Bool' = Either () ()
bool2bool' False = Left ()
bool2bool' False = Right ()
bool'2bool (Left ()) = False
bool'2bool (Right ()) = True
O tipo Either
define a soma de dois tipos ou a união disjunta do conjunto
definido por dois tipos.
Either
:
data Dir = Norte | Sul | Leste | Oeste
data Dir' = Either (Either ()) (Either ())
norte, sul, leste, oeste :: Dir'
norte = Left (Left ())
sul = Left (Right ())
leste = Right (Left ())
oeste = Right (Right ())
Char
possui \(256\) caracteres
quantos valores possui o tipo Cod_Prod
?
data Cod_Prod = Either Char Char
Resposta:
data Cod_Prod = Either Char Char
Pair
, vamos definir
o produto de Void
e ()
:
data ZeroVezesUm = Pair Void ()
Resposta:
Void
:
data ZeroVezesUm = Pair Void ()
data UmVezesUm = Pair () ()
Pair () ()
.Pair
:
data Pontos3D = P Double Double Double
type Pontos3D' = Pair Double (Pair Double Double)
p2p' (P x y z) = Pair x (Pair y z)
p'2p (Pair x (Pair y z)) = P x y z
Char
possui \(256\) caracteres
quantos valores possui o tipo Valida_Cod
?
data Valida_Cod = Pair Char Bool
Resposta:
data Valida_Cod = Pair Char Bool
data Cod_Prod = Either Char Char
data Valida_Cod = Pair Char Bool
data Cod_Prod = Either Char Char
data Valida_Cod = Pair Char Bool
prod2valida (Left c) = Pair c False
prod2valida (Right c) = Pair c True
valida2prod (Pair c False) = Left c
valida2prod (Pair c True) = Right c
data Tipo1 x y z = Pair (Either x y) z
data Tipo2 x y z = Either (Pair x z) (Pair y z)
t1t2 (Pair (Left x) z) = Left (Pair x z)
t1t2 (Pair (Right y) z) = Right (Pair y z)
t2t1 (Left (Pair x z)) = (Pair (Left x) z)
t2t1 (Right (Pair y z)) = (Pair (Right y) z)
f :: a -> b -- b^a
Bool -> a
possui \(a^2\)
combinações possíveis de entrada e saída. Por exemplo,
quantas definições existem para Bool -> Bool
?id :: Bool -> Bool
id False = False
id True = True
allFalse :: Bool -> Bool
allFalse False = False
allFalse True = False
allTrue :: Bool -> Bool
allTrue False = True
allTrue True = True
not :: Bool -> Bool
not False = True
not True = False
O tipo da função restringe as possíveis implementações dela, com isso alguns erros de implementação podem ser verificados em tempo de compilação.
Se a função contém tipos paramétricos, ela se torna ainda mais restrita!
data List a = Empty | Cons a (List a)
List a
como \(L(a)\), Empty
como \(1\), temos \(L(a) = 1 + a \cdot L(a)\).data List a = Empty | Cons a (List a)
\begin{align} L(a) &= 1 + a \cdot L(a) \\ L(a) - a \cdot L(a) &= 1 \\ (1 - a) \cdot L(a) &= 1 \\ L(a) &= \frac{1}{1 - a} \end{align}
Empty
),
ou de \(1\) elemento, ou \(2\), ou \(3\), etc.Será que podemos derivar um ADT? 🤔
\begin{equation*} L’(a) = \frac{1}{(1 - a)^2} = \frac{1}{1 - a} \times \frac{1}{1 - a} \end{equation*}
type DiffList a = Pair (List a) (List a)
data Zipper a = Z [a] [a]
toZipper :: [a] -> Zipper a
toZipper xs = Z [] xs
fromZipper :: Zipper a -> [a]
fromZipper (Z lft rgt) = reverse lft ++ rgt
data Zipper a = Z [a] [a]
focus :: Zipper a -> a
focus (Z _ []) = error "Lista vazia!"
focus (Z _ (x:xs)) = x
walkRight, walkLeft :: Zipper a -> Zipper a
walkRight (Z lft (x:rgt)) = Z (x:lft) rgt
walkLeft (Z (x:lft) rgt) = Z lft (x:rgt)
data Tree a = Leaf | Node { left :: Tree a
, node :: a
, right :: Tree a
}
\begin{align*} T’(a) &= T(a)^2 + 2 \cdot a \cdot T(a) \cdot T’(a) \\ T’(a) &= T(a)^2 \cdot \frac{1}{1 - 2\cdot a \cdot T(a)} \\ \end{align*}
data From = Lft | Rgt
data Zipper a = Zipper { left :: Tree a
, right :: Tree a
, focus :: [(From, a, Tree a)]
}
Olhando para essa definição, vamos analisar as possíveis configurações com diferentes quantidade de árvores:
\(1 + 2^0T^1 + 2^1T^2 + 2^2T^3 + \ldots\)
que é a série:
\(\sum_{i=1}{2^{i-1}T^i} = \frac{T}{1 - 2T}\)
data Zipper a = Zipper { focus :: Tree a
, histo :: [Either (Tree a) (Tree a)]
} deriving Show
Zipper
basta colocar os ramos
da esquerda e da direita em seus respectivos campos e criar
uma lista contendo o nó raiz (os outros elementos da tupla não
importam).
toZipper :: Tree a -> Zipper a
toZipper Leaf = Zipper Leaf []
toZipper n = Zipper n []
Zipper
para um Tree
requer que
estejamos com o foco na raíz. Para isso, basta caminharmos
para cima até chegar a um único elemento na lista de focos.
fromZipper :: Zipper a -> Tree a
fromZipper tz =
case histo tz of
[] -> focus tz
_ -> fromZipper (goUp tz)
goLeft :: Zipper a -> Zipper a
goLeft tz =
case focus tz of
Node Leaf _ _ -> tz
Node l x r -> Zipper l (Left (Node Leaf x r) : histo tz)
goRight :: Zipper a -> Zipper a
goRight tz =
case focus tz of
Node _ _ Leaf -> tz
Node l x r -> Zipper r (Right (Node l x Leaf) : histo tz)
Para andar para cima basta recuperarmos a informação do primeiro elemento da lista de históricos.
goUp :: Zipper a -> Zipper a
goUp (Zipper f []) = Zipper f []
goUp (Zipper f (h:hs)) =
case h of
Left (Node _ x r) -> Zipper (Node f x r) hs
Right (Node l x _) -> Zipper (Node l x f) hs
Aprendemos em uma aula anterior sobre as classes de tipo, classes que definem grupos de tipos que devem conter algumas funções especificadas.
Para criar uma nova classe de tipos utilizamos a palavra
reservada class
:
class Eq a where
(==), (/=) :: a -> a -> Bool
x /= y = not (x == y)
a
pertencer a classe Eq
deve ter uma implementação das funções (==)
e (/=)
.class Eq a where
(==), (/=) :: a -> a -> Bool
x /= y = not (x == y)
(/=)
,
então basta definir (==)
.class Eq a where
(==), (/=) :: a -> a -> Bool
x /= y = not (x == y)
instance Eq Bool where
False == False = True
True == True = True
_ == _ = False
data
e newtype
podem ser
instâncias de alguma classe.Ord
:class Eq a => Ord a where
(<), (<=), (>), (>=) :: a -> a -> Bool
min, max :: a -> a -> a
min x y | x <= y = x
| otherwise = y
max x y | x <= y = y
| otherwise = x
Ord
, o tipo deve ser
também instância de Eq
.Tire da cabeça os conceitos de Classe e Instância vindos de orientação a objetos. Aqui uma classe de tipos nada mais é que um conjunto, uma coleção, uma lista de tipos. Por sua vez, uma instância nada mais é que dizer que um tipo pertence a uma classe, ou seja, está dentro daquele conjunto de classes.
Seguindo nosso exemplo de Booleano, temos:
instance Ord Bool where
False < True = True
_ < _ = False
b <= c = (b < c) || (b == c)
b > c = c < b
b >= c = c <= b
Lembrando:
Eq
- classe da igualdadeTipos que podem ser comparados em igualdade e desigualdade:
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
1 == 2
Saída:
[1,2,3] == [1,2,3]
Saída:
"Ola" /= "Alo"
Saída:
Ord
- classe de ordemA classe Eq
acrescido de operadores de ordem:
(<) :: a -> a -> Bool
(<=) :: a -> a -> Bool
(>) :: a -> a -> Bool
(>=) :: a -> a -> Bool
min :: a -> a -> a
max :: a -> a -> a
4 < 6
Saída:
min 5 0
Saída:
max 'c' 'h'
Saída:
"Ola" <= "Olaf"
Saída:
Show
- classe imprimíveisA classe Show
define como imprimir um valor de um tipo:
show :: a -> String
show 10.0
Saída:
show [1,2,3,4]
Saída:
Read
- classe legíveisA classe Read
define como ler um valor de uma String:
read :: String -> a
Precisamos especificar o tipo que queremos extrair da String:
read "12.5" :: Double
Saída:
read "False" :: Bool
Saída:
read "[1,3,4]" :: [Int]
Saída:
Num
- classe numéricaA classe Num
define todos os tipos numéricos:
(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
1 + 3
Saída:
6 - 9
Saída:
3 * (-2)
Saída:
Valores negativos devem ser escritos entre parênteses para remover ambiguidades com o operador de subtração.
Integral
- classe de números inteirosA classe Integral
define todos os tipos numéricos inteiros:
quot :: a -> a -> a
rem :: a -> a -> a
div :: a -> a -> a
mod :: a -> a -> a
quotRem :: a -> a -> (a, a)
divMod :: a -> a -> (a, a)
toInteger :: a -> Integer
10 `quot` 3
Saída:
10 `rem` 3
Saída:
10 `div` 3
Saída:
10 `mod` 3
Saída:
As funções quot
e rem
arredondam para o \(0\), enquanto div
e
mod
para \(-\infty\).
Fractional
- classe de números racionaisFractional
define todos os tipos numéricos
fracionários(/) :: a -> a -> a
recip :: a -> a
10 / 3
Saída:
recip 10
Saída:
Qual a diferença entre esses dois operadores de exponenciação?
(^) :: (Num a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a
Floating
- classe de números de ponto flutuanteclass Fractional a => Floating a where
pi :: a
exp :: a -> a
log :: a -> a
sqrt :: a -> a
(**) :: a -> a -> a
logBase :: a -> a -> a
sin :: a -> a
cos :: a -> a
tan :: a -> a
asin :: a -> a
acos :: a -> a
atan :: a -> a
sinh :: a -> a
cosh :: a -> a
tanh :: a -> a
asinh :: a -> a
acosh :: a -> a
atanh :: a -> a
:info
mostra informações sobre os tipos e as
classes de tipo:> :info Integral
class (Real a, Enum a) => Integral a where
quot :: a -> a -> a
rem :: a -> a -> a
div :: a -> a -> a
mod :: a -> a -> a
quotRem :: a -> a -> (a, a)
divMod :: a -> a -> (a, a)
toInteger :: a -> Integer
{-# MINIMAL quotRem, toInteger #-}
:info
mostra informações sobre os tipos e as
classes de tipo:> :info Bool
data Bool = False | True -- Defined in ‘GHC.Types’
instance Eq Bool -- Defined in ‘GHC.Classes’
instance Ord Bool -- Defined in ‘GHC.Classes’
instance Show Bool -- Defined in ‘GHC.Show’
instance Read Bool -- Defined in ‘GHC.Read’
instance Enum Bool -- Defined in ‘GHC.Enum’
instance Bounded Bool -- Defined in ‘GHC.Enum’
deriving
ao definir um novo tipo:data Bool = False | True
deriving (Eq, Ord, Show, Read)
Enum
succ, pred, toEnum, fromEnum
data Dias = Dom | Seg | Ter | Qua | Qui | Sex | Sab
deriving (Show, Enum)
Enum
é enumerativo:
succ Seg == Ter
pred Ter == Seg
fromEnum Seg == 0
toEnum 1 :: Dias == Ter
-- E pode-se fazer
[Seg .. Sex] == [Seg, Ter, Qua, Qui, Sex]
Defina um tipo para jogar o jogo Pedra, Papel e Tesoura e defina
as funções ganhaDe
, perdeDe
.
Defina também uma função denominada ganhadores
que recebe uma
lista de jogadas e retorna uma lista dos índices das jogadas
vencedoras.
data Jogada = Pedra | Papel | Tesoura
deriving (Show, Enum, Eq)
ganhaDe :: Jogada -> Jogada -> Bool
Pedra `ganhaDe` Tesoura = True
Papel `ganhaDe` Pedra = True
Tesoura `ganhaDe` Papel = True
j1 `ganhaDe` j2 | j1 == j2 = True
| otherwise = False
data Jogada = Pedra | Papel | Tesoura
deriving (Show, Enum, Eq)
perdeDe :: Jogada -> Jogada -> Bool
j1 `perdeDe` j2 = not $ j1 `ganhaDe` j2
listaDeDouble :: [Double]
talvezInt :: Maybe Int
arvoreChar :: Tree Char
[a], Maybe a, Tree a, ...
dobra :: Int -> Int
dobra x = 2*x
flip :: Coin -> Coin
flip Cara = Coroa
flip Coroa = Cara
dobraLista :: [Int] -> [Int]
dobraLista = map (*2)
flipLista :: [Coin] -> [Coin]
flipLista = map flip
Maybe Coin
, ou Tree Int
?dobraArvore :: Tree Int -> Tree Int
dobraArvore = ??
flipMaybe :: Maybe Coin -> Maybe Coin
flipMaybe = ??
Estes slides foram preparados para os cursos de Paradigmas de Programação e Desenvolvimento Orientado a Tipos na UFABC.
Este material pode ser usado livremente desde que sejam mantidos, além deste aviso, os créditos aos autores e instituições.