Polimorfismo, classes e inferência de tipos

Deduzir tipos gerais pelas aplicações, padrões e operações usadas, sem confundir classes com tipos.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

trocar (x,y) = (y,x) pode trocar um caráter e um booleano sem saber o que significam. O tipo (a,b) -> (b,a) conserva essa liberdade. Em duplicar x = (x,x), as duas posições recebem o mesmo valor, por isso têm o mesmo tipo: a -> (a,a). As letras são variáveis de tipo, distintas dos nomes x e y no programa.

Generalidade sem operações específicas

trocar (x,y) = (y,x)
duplicar x = (x,x)
primeiro (x:_) = x

As funções apenas deslocam ou selecionam valores, por isso os tipos mais gerais são (a,b) -> (b,a), a -> (a,a) e [a] -> a. A última função é parcial: a generalidade do tipo não resolve a ausência de uma equação para []. Um tipo pode estar certo mesmo que a função falhe ou não termine para certas entradas.

Uma lista impõe o mesmo tipo aos elementos. Uma lista de funções também segue essa regra: [reverse,tail] pode ter tipo [[a] -> [a]]. Cada elemento é uma função de lista para lista; a lista exterior não é uma função. Já (reverse,length) é um par e pode juntar resultados de tipos diferentes.

Operações acrescentam restrições

contem x xs = x `elem` xs
maior x y = if x >= y then x else y
media x y = (x+y)/2

elem compara valores, por isso contem :: Eq a => a -> [a] -> Bool. A comparação >= pede Ord a, dando maior :: Ord a => a -> a -> a. A divisão / pede Fractional a, que já exige Num a; por isso media :: Fractional a => a -> a -> a. Não é preciso acumular a restrição redundante Num a nessa assinatura.

ClasseOperações ou capacidade
Eq==, /=; igualdade disponível para esse tipo.
Ord<, <=, compare; também exige Eq.
Showshow; transformar um valor em texto.
Readread; interpretar texto, com risco de erro se o texto não servir.
Num+, -, *, abs, literais inteiros.
Integraldiv, mod, quot, rem; inclui Int e Integer.
Fractional/, recip, literais fracionários.
Floatingsqrt, sin, exp; exige Fractional.

Uma classe agrupa tipos com operações comuns, ou seja não é uma classe de objetos com herança e campos. Num nos GHC atuais não tem Eq como superclasse. Precisar de aritmética e de igualdade pode portanto exigir (Num a, Eq a). Uma árvore da hierarquia antiga não deve ser usada para retirar essa restrição.

Deduzir um tipo passo a passo

Aplicação repetida

Para duas f x = f (f x), começa com x :: a e f :: b -> c. As duas chamadas usam o mesmo f:

AplicaçãoRestriçãoResultado
f xb = ac
f (f x)c = bc

As igualdades dão a = b = c, logo duas :: (a -> a) -> a -> a. Não há operações sobre os valores, por isso não precisamos de uma classe; instanciar a com Int permite usar (+1) e com Bool permite usar not.

Duas medianas com restrições diferentes

Compara duas formas de calcular a mediana de três valores. Por comparações, basta ordenar só com (<=):

inserirOrd :: Ord a => a -> [a] -> [a]
inserirOrd x [] = [x]
inserirOrd x (y:ys)
  | x <= y    = x : y : ys
  | otherwise = y : inserirOrd x ys

ordenar3 :: Ord a => a -> a -> a -> [a]
ordenar3 a b c = inserirOrd c (inserirOrd b [a])

medianaOrd :: Ord a => a -> a -> a -> a
medianaOrd x y z = head (tail (ordenar3 x y z))

ordenar3 3 1 2 insere 1 em [3] para [1,3] e depois 2 para [1,2,3], dando mediana 2; só usa (<=), por isso só pede Ord a. Se calculares pela soma e pelos extremos, como (x + y + z - minimum [x,y,z] - maximum [x,y,z]), usas (+) e (-) e o tipo passa a (Ord a, Num a) => a -> a -> a -> a, excluindo tipos ordenados sem aritmética como listas de carateres. Lê a restrição como registo das operações usadas.

A versão aritmética também depende das operações concretas do tipo. Em vírgula flutuante, o arredondamento pode perder informação. Com x=1e20, y=1 e z=2, a soma dos três arredonda para 1e20; retirar o máximo e o mínimo já não recupera a mediana 2.

O tipo permite a operação, mas não prova a equivalência matemática para todos os valores. Lê a restrição como registo das operações usadas, não como garantia de que duas fórmulas coincidem em todos os tipos.

Em selecionar p xs = [x | x <- xs, p x], se xs :: [a] então cada x :: a. Como a condição da compreensão tem tipo Bool, p :: a -> Bool e o resultado volta a ser [a], dando (a -> Bool) -> [a] -> [a]. A compreensão não acrescenta restrições: lê-as nas funções usadas dentro dela.

Em f x y = [x,y+1], a lista iguala os tipos de x e y, por isso + acrescenta Num a sem obrigar a Int. O tipo mais geral é Num a => a -> a -> [a]. Uma assinatura mais específica pode compilar, mas não responde ao pedido de tipo mais geral.

Aplicação e setas

a -> b -> c significa a -> (b -> c): a função recebe um a e devolve uma função de b para c. A aplicação associa no sentido oposto: f x y significa (f x) y. O tipo (a -> b) -> a -> b recebe uma função no primeiro argumento; retirar esses parênteses muda o tipo.

O operador . exige que a saída da segunda função possa entrar na primeira:

(.) :: (b -> c) -> (a -> b) -> a -> c

length . filter even recebe uma lista de inteiros e devolve Int, com a restrição Integral a vinda de even, pois length não inspeciona os elementos; o tipo especializado a listas é Integral a => [a] -> Int.

Números e conversões

O literal 3 pode ser usado em qualquer instância de Num; 3.5 exige Fractional, por isso a expressão length xs + 0.5 falha porque length xs :: Int, e Int não é Fractional. Usa fromIntegral (length xs) para obter um valor compatível com a divisão ou soma fracionária. A conversão não acontece automaticamente.

div e mod arredondam o quociente para baixo; quot e rem truncam-no para zero. Para -7 e 3, temos div=-3, mod=2, quot=-2, rem=-1. Em ambos os pares vale a = q*b+r, com b diferente de zero. Escolher um par e depois usar o resto do outro quebra essa relação.

Instanciar tipos e comparar divisões
duasVezes :: (a -> a) -> a -> a
duasVezes f x = f (f x)
main :: IO ()
main = do
  print (duasVezes (+1) (4::Int))
  print (duasVezes not True)
  print ((-7) `div` 3, (-7) `mod` 3, (-7) `quot` 3, (-7) `rem` 3)
Resultado

Executa o código para ver a saída e os gráficos.

Para conferir a tua dedução, usa :t nome no GHCi. Dependendo da versão, funções como length e elem podem aparecer generalizadas a Foldable, em vez de listas. A instância especializada a [a] usada nas aulas continua válida; não interpretes essa diferença como um erro da tua dedução.

Exercícios

Tipo de uma aplicação repetida

Qual é o tipo mais geral de duas f x = f (f x)?

A tua resposta

Pista

Usa o mesmo tipo de f nas duas chamadas.

Solução

Se x é a e f é a -> b, a chamada exterior exige b=a. A função não usa operações de classe.

  • (a -> a) -> a -> a. A saída da chamada interior volta a entrar na mesma função.
  • (a -> b) -> a -> b. Não obriga b a poder entrar em f.
  • Num a => (a -> a) -> a -> a. A restrição numérica não é usada pelo corpo.

Erros frequentes

Uma função que funciona com números pode ainda ter um tipo mais geral.

Voltar à explicação
Que restrição pede a divisão?

Deduz o tipo mais geral de meio x = x/2 e explica por que Int não serve.

Pista

Começa pelo tipo de (/).

Solução

meio :: Fractional a => a -> a. O literal 2 pode entrar nesse a; Int não tem a instância Fractional necessária.

Confere a tua resposta:

  • Obtenho Fractional a => a -> a.
  • Relaciono entrada e resultado pelo mesmo a.
  • Distingo literal inteiro de divisão fracionária.

Erros frequentes

Não acrescentes Num redundante nem escolhas Double só porque é um exemplo possível.

Voltar à explicação
Divisão com número negativo

Quanto vale (-11) mod 4?

Pista

div arredonda o quociente para baixo.

Solução

O resto é 1. Com quot, o quociente seria -2 e rem seria -3.

Erros frequentes

Não uses o quociente de quot com o resto de mod.

Voltar à explicação
Restrições que vêm de funções diferentes

Deduz o tipo mais geral de somaSem xs = sum (semRepetidos xs). Assume semRepetidos :: Eq a => [a] -> [a]. Que restrições vêm de cada função?

Pista

O filtro conserva o tipo dos elementos.

Mais uma pista

semRepetidos pede igualdade; sum pede aritmética.

Solução

O tipo é (Eq a, Num a) => [a] -> a. semRepetidos usa Eq a e conserva [a]; sum recebe essa lista e usa Num a. Ambas as restrições incidem no mesmo tipo de elemento.

Confere a tua resposta:

  • Obtenho (Eq a, Num a) => [a] -> a.
  • Relaciono Eq com semRepetidos e Num com sum.
  • Não deduzo Eq a apenas de Num a.

Erros frequentes

A hierarquia atual não permite retirar Eq a por haver Num a.

Voltar à explicação
Comparar os tipos das medianas

Para três valores, considera medianaOrd, que ordena usando apenas (<=) e seleciona o valor central, e medianaSoma x y z = x+y+z - minimum [x,y,z] - maximum [x,y,z].

Deduz os tipos mais gerais. Qual versão permite obter a mediana de 'c', 'a' e 'b'? Justifica sem restringir os tipos a números.

Pista
Relaciona cada operação com a classe que a fornece.
Solução

Na primeira versão, a comparação impõe Ord a; selecionar um elemento conserva a. O tipo é Ord a => a -> a -> a -> a.

Na segunda, os extremos pedem Ord a e + e - pedem Num a. As três entradas e a saída partilham a: (Ord a, Num a) => a -> a -> a -> a.

Char tem ordenação, mas não tem a instância numérica pedida. A primeira versão dá 'b'; a segunda não aceita esta aplicação.

Confere a tua resposta:

  • Deduzo Ord a para a versão por comparações.
  • Deduzo (Ord a, Num a) para a versão aritmética.
  • Dou uma aplicação permitida só pela primeira versão.

Erros frequentes

O facto de uma aplicação usar inteiros não torna Int o tipo mais geral.
Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.