Exercícios
Práticos - 1

Frank Coelho de Alcantara - 2021    

Last usando foldr

              
                module Main where
                
                import Data.Maybe
                import Control.Applicative ((<|>))
                
                last_1 :: [a] -> a -- definição do tipo
                -- uma função com muitas novidades
                last_1 = fromJust . foldr (\x y -> y <|> Just x) Nothing 
                
                main::IO()
                main = do
                  print "coisa"
                  print (last_1 [1,2,3,4,567]) --teste da função
              
          

Em uma linha de código muita coisa nova. Vamos começar usando uma versão mais simples desta implementação.

Last_4


          module Main where
          import Data.Maybe
          import Control.Applicative ((<|>))
                  
          last_4 :: [a] -> a --definição de tipo
          last_4 xs =
            case foldr go Nothing xs of
              Nothing -> error "last: lista vazia"
              Just x -> x
            where
              go x (Just y) = Just y
              go x Nothing = Just x
          
          main::IO()
          main = do
            print (last_4 [1,2,3,4,567]) --resultado 567
          

Tente este código em um ambiente online de Haskell.

Case

Um case deve ter, no mínimo, uma alternativa. Cada alternativa deve ter um corpo. Todos os corpos devem ser do mesmo tipo e este será o tipo do case.

            
              module Main where

              coisa x = case x of
                1 -> "A"
                2 -> "B"
                3 -> "C"
              --que poderia ser escrito como:  
              coisa2 1 = "A"
              coisa2 2 = "B"
              coisa2 3 = "C"

              main::IO()
              main = do
                print (coisa 3) --"C"
                print (coisa2 3) --"C"
            
          

Case Exercicio 1

Defina uma função caseTail que devolva o tail de uma lista usando a expressão case. Você tem 3 minutos para isso.

              
                module Main where
                      
                caseTail :: [a] -> [a]
                caseTail x =
                  case x of
                    [] -> error "lista vazia não tem tail"
                    (x:xs) -> xs
                
                main::IO()
                main = do
                  print (caseTail [1,2,3,4,567])
              
            

Case Exemplo 2

Escreva uma função, que recebendo o código de um estado (RJ e PR) devolva o string: vai visitar o (nome do estado por extenso), usando o case. Vamos ver o código.

                    
                      module Main where
                      -- apenas para converter código em extenso       
                      parseEstado :: String -> Maybe String
                      parseEstado "RJ" = Just "Rio de Janeiro"
                      parseEstado "PR" = Just "Paraná"
                      parseEstado _ = Nothing
                      -- usando o case
                      visitar :: String -> String
                      visitar codigoEstado = case parseEstado codigoEstado of
                        Just estado -> "vai visitar o " ++ estado
                        Nothing -> "resultado desconhecido"
                      -- usando uma função de ajuda                   
                      visitar2 :: String -> String
                      visitar2 codigoEstado = tratando (parseEstado codigoEstado)
                        where
                          tratando (Just estado) = "vai visitar o " ++ estado
                          tratando Nothing = "resultado desconhecido"
                      
                      main::IO()
                      main = do
                        print (visitar "RJ")
                        print (visitar "AM")
                        print (visitar2 "RJ")
                        print (visitar2 "AM")
                    
                  

Case Exercício 2

Escreva uma função, usando case, que determine se uma determinada frase é uma declaração, uma pergunta ou uma exclamação. Escreva também esta mesma função usando uma função de ajuda. Você tem 10 minutos para isso.

              
                module Main where
                      
                tipoSentenca :: String -> String
                tipoSentenca sentenca = case (last sentenca) of
                  '.' -> "declaracao"
                  '?' -> "questao"
                  '!' -> "exclamacao"
                  _ -> "erro de sintaxe"
                
                tipoSentenca2 :: String -> String
                tipoSentenca2 sentenca = classifica (last sentenca)
                  where
                    classifica '.' = "declaracao"
                    classifica '?' = "questao"
                    classifica '!' = "exclamacao"
                    classifica _ = "erro de sintaxe"
                
                main::IO()
                main = do
                  print (tipoSentenca "isso é uma pergunta?")
                  print (tipoSentenca2 "isso é uma pergunta!")
              
            

Where

Usamos where, entre outras coisas, para definir expressões intermediárias. Pegue, por exemplo, uma função qualquer:

              
                qualquerFunc :: Int -> Int -> Int -> Int
                qualquerFunc a b c = (c - a) + (b - a) + (a + b + c) + a
              
            

Podemos reescrever usando where e funções intermediárias, que só existem neste escopo.

              
                qualquerFunc2 :: Int -> Int -> Int -> Int
                qualquerFunc2 a b c = dif1 + dif2 + som1 + a
                  where
                    diff1 = c - a
                    diff2 = b - a
                    som1 = a + b + c
              
            

A ordem das funções não importa. E podemos usar where para declarar funções dentro de funções.

Let

Podemos usar o let de forma muito parecida, com exceção que precisaremos do in e a ordem das funções intermediárias importa na definição.

              
                qualquerFunc2 :: Int -> Int -> Int -> Int
                qualquerFunc2 a b c = 
                  let 
                    diff1 = c - a
                    diff2 = b - a
                    som1 = a + b + c
                  in 
                    dif1 + dif2 + som1 + a
              
              

Quando usamos $IO()$ não usamos o in. E podemos usar o let para definir a ligação de variáveis e funções.

            
              let formula = 7 * (let x = 3 in x + 100) + 2
            
            

Let, where Exercício 1

O índice de massa muscular (IMM), uma lenda ainda usada pelos médicos, é o resultado da divisão do seu peso pelo quadrado da sua altura. Se ele for menor que 18.5, você está abaixo do peso. Se este índice estiver entre 18.5 e 25 você tem peso normal. Entre 25 e 30 você estará acima do peso mais que isso será obeso. Usando, obrigatoriamente where, ou let faça, em Haskell uma função para determinar o IMM de um indivíduo. Lembrando que o resultado precisa indicar: abaixo do peso, normal, acima do peso ou obeso. Você tem 15 minutos para isso.

Resultados inexistentes

Eventualmente encontramos uma situação, onde o valor desejado simplesmente não existe. Em linguagens de programação imperativas lidamos com isso de duas formas:

  1. Retornamos um erro: cabe ao programador prever o problema, capturar o erro e emitir algum tipo de alerta de exceção. Alguma coisa como $try \space \space catch$ em C++. Um dos problemas que esta solução enfrenta é manter o erro informativo e, tratar cada erro possível e imaginável, na forma adequada.
  2. Retornamos null: ou nil, ou qualquer valor que indique inexistência. Neste caso, também cabe ao programador verificar o valor null em todos os lugares onde este valor possa ser usado. Além disso, os valores null são complexos e se comportam de forma diferente dos valores tradiconais.

O Haskell resolve este problema usando o tipo Maybe, definido como:

              
                data Maybe a = Nothing | Just a
              
            

Maybe

No caso da definição do tipo Maybe usamos dois outros tipos:

              
                data Maybe a = Nothing | Just a
              
            

O data indica que estamos definindo um novo tipo de dado. A barra vertical $|$ representa seleção, e pode ser lida como ou. O $a$ é o parâmetro do tipo. Ou seja, definimos um tipo, Maybe, que aplicado a um valor $a$ qualquer pode retornar Nothing ou Just a.

Just a representa o próprio valor $a$, enquanto Nothing representa nada do tipo do valor $a$.

Maybe é um construtor de tipos. Se passarmos um char como parâmetro, teremos um tipo Maybe Char. Se o $a$ for um inteiro termos o tipo Maybe Int... Nothing, por sua vez é um tipo polimórfico ele pode ser de qualquer um dos tipos estanciados por Maybe a e não requer parâmetros. Just também é polimórfico mas requer o parâmetro $a$ e será do mesmos tipo criado pelo Maybe a.

Tipos paramétricos em C++ são chamados de templates, em java de generics

Exemplo Maybe

Tomemos como exemplo a definição da função find

              
                -- | Find the first element from the list for which the predicate function
                -- returns True. Return Nothing if there is no such element.
                find :: (a -> Bool) -> [a] -> Maybe a
                find _ [] = Nothing
                find predicate (first:rest) =
                  if predicate first
                    then Just first
                    else find predicate rest
              
            

Voltando ao Last_4


                module Main where
                import Data.Maybe
                import Control.Applicative ((<|>))
                        
                last_4 :: [a] -> a --definição de tipo
                last_4 xs =
                  case foldr go Nothing xs of
                    Nothing -> error "last: lista vazia"
                    Just x -> x
                  where
                    go x (Just y) = Just y
                    go x Nothing = Just x
                
                main::IO()
                main = do
                  print (last_4 [1,2,3,4,567]) --resultado 567
                

Podemos simplificar um pouco.

Last_3

            
              module Main where
              import Data.Maybe
              import Control.Applicative ((<|>))
                      
              last_3 :: [a] -> a
              last_3 xs =
              case foldr (\x y -> y <|> Just x) Nothing xs of
                Nothing -> error "last: lista vazia"
                Just x -> x
            
              main::IO()
              main = do
                print (last_3 [1,2,3,4,567]) --resultado 567
            
          

Reduzimos a função $go$. Observe o operador alternative $<|>$. Agora quero tirar o case.

Last_2

          
            module Main where
            import Data.Maybe
            import Control.Applicative ((<|>))
                    
            last_2 :: [a] -> a
            last_2 xs = fromJust $ foldr (\x y -> y <|> Just x) Nothing xs

            -- que é a mesma coisa que:
            last_2' :: [a] -> a
            last_2' xs = fromJust (foldr (\x y -> y <|> Just x) Nothing xs)
          
            main::IO()
            main = do
              print (last_2 [1,2,3,4,567]) --resultado 567
              print (last_2' [1,2,3,4,567]) --resultado 567
          
        

O operador $\$$, operador de aplicação, pode ser usado para definir precedência e é mais "elegante" que o uso dos parênteses.

Finalmente Last_1

              
                module Main where
                import Data.Maybe
                import Control.Applicative ((<|>))
                        
                last_1 :: [a] -> a
                last_1 = fromJust . foldr (\x y -> y <|> Just x) Nothing
              
                main::IO()
                main = do
                  print (last_1 [1,2,3,4,567]) --resultado 567
              
            

E agora, do nada apareceu um ".".

Composição de Funções

          
              module Main where
              -- (.) :: (b -> c) -> (a -> b) -> a -> c
              
              f :: Int -> Int
              f x = * 2 x
              
              g :: Int -> Int
              g x = x - 1
            
              h = f.g
            main::IO()
              main = do
                print (h 5) --resultado 8
                print ((f.g) 5) --resultado 8
          
        

Exercício

Explique, detalhadamente, as seguintes soluções:

          
              module Main where
              -- implementando head
              head_1 :: [a] -> a
              head_1 = foldr (\x xs -> x) (error "head: lista vazia")
              
              -- implementado tail
              tail_1 :: [a] -> Maybe (a, [a])
              tail_1 = foldr go Nothing where
                go x Nothing = Just (x, [])
                go head (Just (head', tail)) = Just (head, head':tail)
              
              main::IO()
              main = do
                print (head_1 [1, 3, 4, 12, 675])
                print (head_1 ['1', '3', '4'])
                print (tail_1 [1, 2, 3])