Frank Coelho de Alcantara - 2021
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.
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.
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"
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])
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")
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!")
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.
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
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.
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:
O Haskell resolve este problema usando o tipo Maybe, definido como:
data Maybe a = Nothing | Just a
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
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
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.
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.
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.
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 ".".
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
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])