Free Palestine and Lebanon 🍉 Stop the Genocide
Haskell Logo

Programação Funcional

Ficha 8: Classes de tipos

Voltar

1) Considere o seguinte tipo de dados para representar frações

data Frac = F Integer Integer

a) Defina a função normaliza :: Frac -> Frac que dada uma fração calcula uma fração equivalente, irredutível, e com o denominador positivo. Por exemplo, normaliza (F (-33) (-51)) deve retornar F 11 17 e normaliza (F 50 (-5)) deve retornar F (-10) 1. Sugere-se que comece por definir primeiro a função mdc :: Integer -> Integer -> Integer que calcula o máximo divisor comum entre dois números, baseada na seguinte propriedade (atribuida a Euclides): mdc x y == mdc (x+y) y == mdc x (y+x)

normaliza :: Frac -> Frac
normaliza (F a b)
    | b < 0 = normaliza $ F (-a) (-b)
    | otherwise = 
        let d = mdc a b in
        F (a `div` d) (b `div` d)

mdc :: Integer -> Integer -> Integer
mdc x 0 = x
mdc 0 y = y
mdc x y = mdc y (x `mod` y)

b) Defina Frac como instância da classe Eq.

instance Eq Frac where
    (==) :: Frac -> Frac -> Bool
    f1 == f2 = a1 == a2 && b1 == b2
        where F a1 b1 = normaliza f1
              F a2 b2 = normaliza f2

c) Defina Frac como instância da classe Ord.

instance Ord Frac where
    (<=) :: Frac -> Frac -> Bool
    f1 <= f2 = a1 * b2 <= a2 * b1
        where F a1 b1 = normaliza f1
              F a2 b2 = normaliza f2

d) Defina Frac como instância da classe Show, de forma a que cada fracção seja apresentada por (numerador/denominador).

instance Show Frac where
    show :: Frac -> String
    show f = show a ++ "/" ++ show b
        where F a b = normaliza f

e) Defina Frac como instância da classe Num. Relembre que a classe Num tem a seguinte definição:

class Num a where
    (+), (*), (-) :: a -> a -> a
    negate, abs, signum :: a -> a
    fromInteger :: Integer -> a
instance Num Frac where
    (+) :: Frac -> Frac -> Frac
    (F a b) + (F c d) = normaliza $ F (a * d + b * c) (b * d)
    
    (-) :: Frac -> Frac -> Frac
    x - y = x + negate y

    (*) :: Frac -> Frac -> Frac
    (F a b) * (F c d) = normaliza $ F (a * c) (b * d)
    
    negate :: Frac -> Frac
    negate (F a b) = normaliza $ F (-a) b
    
    abs :: Frac -> Frac
    abs f = F (abs a) b
        where F a b = normaliza f
    
    signum :: Frac -> Frac
    signum f = F (signum a) 1
        where F a b = normaliza f
    
    fromInteger :: Integer -> Frac
    fromInteger x = F x 1

f) Defina uma função que, dada uma fração f e uma lista de frações l, seleciona de l os elementos que são maiores do que o dobro de f.

maioresQueDobro :: Frac -> [Frac] -> [Frac]
maioresQueDobro = filter . (<) . (2 *)

2) Relembre o tipo definido na Ficha 7 para representar expressões inteiras. Uma possível generalização desse tipo de dados será considerar expressões cujas constantes são de um qualquer tipo numérico (i.e., da classe Num).

data Exp a = Const a
| Simetrico (Exp a)
| Mais (Exp a) (Exp a)
| Menos (Exp a) (Exp a)
| Mult (Exp a) (Exp a)

a) Declare Exp a como uma instância de Show.

instance Show a => Show (Exp a) where
    show (Const a) = show a
    show (Simetrico a) = "(- " ++ show a ++ ")"
    show (Mais a b) = "(" ++ show a ++ " + " ++ show b ++ ")"
    show (Menos a b) = "(" ++ show a ++ " - " ++ show b ++ ")"
    show (Mult a b) = "(" ++ show a ++ " * " ++ show b ++ ")"

b) Declare Exp a como uma instância de Eq.

valueOf :: Num a => Exp a -> a
valueOf (Const a) = a
valueOf (Simetrico a) = negate $ valueOf a
valueOf (Mais a b) = valueOf a + valueOf b
valueOf (Menos a b) = valueOf a - valueOf b
valueOf (Mult a b) = valueOf a * valueOf b

instance (Num a, Eq a) => Eq (Exp a) where
    (==) :: (Num a, Eq a) => Exp a -> Exp a -> Bool
    a == b = valueOf a == valueOf b

c) Declare Exp a como instância da classe Num.

instance (Ord a, Num a) => Num (Exp a) where
    (+) :: Num a => Exp a -> Exp a -> Exp a
    x + y = Mais x y
    
    (-) :: Num a => Exp a -> Exp a -> Exp a
    x - y = Menos x y
    
    (*) :: Num a => Exp a -> Exp a -> Exp a
    x * y = Mult x y
    
    negate :: Num a => Exp a -> Exp a
    negate (Simetrico a) = a
    negate a = Simetrico a
    
    fromInteger :: Num a => Integer -> Exp a
    fromInteger x = Const (fromInteger x)
    
    abs :: (Ord a, Num a) => Exp a -> Exp a
    abs (Const a) = Const (abs a)
    abs (Simetrico a) = abs a
    abs op = if valueOf op < 0 
             then negate op
             else op
    
    signum :: Num a => Exp a -> Exp a
    signum a | valueOf a < 0 = Const (-1)
             | valueOf a == 0 = Const 0
             | otherwise = Const 1 

3) Relembre o exercício da Ficha 3 sobre contas bancárias, com a seguinte declaração de tipos:

data Movimento = Credito Float | Debito Float
data Data = D Int Int Int
data Extracto = Ext Float [(Data, String, Movimento)]

a) Defina Data como instância da classe Ord.

instance Ord Data where
    compare :: Data -> Data -> Ordering
    compare (D dia1 mes1 ano1) (D dia2 mes2 ano2) 
        | ano1 > ano2 || ano1 == ano2 && (mes1 > mes2 || mes1 == mes2 && dia1 > dia2) = GT
        | ano1 == ano2 && mes1 == mes2 && dia1 == dia2 = EQ
        | otherwise = LT

b) Defina Data como instância da classe Show.

instance Show Data where 
    show :: Data -> String
    show (D dia mes ano) = intercalate "/" $ map show [ano,mes,dia]

c) Defina a função ordena :: Extracto -> Extracto, que transforma um extrato de modo a que a lista de movimentos apareça ordenada por ordem crescente de data.

ordena :: Extracto -> Extracto
ordena (Ext n l) = Ext n (sortBy (\(data1,_,_) (data2,_,_) -> compare data1 data2) l)

d) Defina Extracto como instância da classe Show de forma a que a apresentação do extracto seja por ordem de data do movimento, com o seguinte aspeto:

Saldo anterior: 300
---------------------------------------
Data      Descricao   Credito   Debito
---------------------------------------
2010/4/5  DEPOSITO    2000
2010/8/10 COMPRA                37,5
2010/9/1  LEV                   60
2011/1/7  JUROS       100
2011/1/22 ANUIDADE              8
---------------------------------------
Saldo atual: 2294,5
instance Show Extracto where
    show :: Extracto -> String
    show ext = "Saldo anterior: " ++ show n ++
               "\n---------------------------------------" ++
               "\nData       Descricao" ++ replicate (desc_max - 9) ' ' ++ "Credito" ++ replicate (cred_max - 7) ' ' ++ "Debito" ++
               "\n---------------------------------------\n" ++
               unlines (map (\(dat,desc,mov) -> 
                    show dat ++ replicate (data_max - length (show dat)) ' ' 
                    ++ map toUpper desc ++ replicate (desc_max - length desc) ' ' 
                    ++ case mov of Credito quant -> show quant ++ replicate (cred_max - length (show quant)) ' '; Debito _ -> replicate cred_max ' '
                    ++ case mov of Debito quant -> show quant; Credito _ -> ""
               ) movs) ++
               "---------------------------------------" ++
               "\nSaldo actual: " ++ show (saldo ext)
        where (Ext n movs) = ordena ext
              data_max = 11
              desc_max = max (length "Descricao   ") (maximum $ map (\(_,desc,_) -> length desc) movs)
              cred_max = max (length "Credito   ") (maximum $ map (\(_,_,mov) -> case mov of Credito x -> length (show x); _ -> 0) movs)