Home

24 dias de Hackage, 2015 - dia 6 - Encontrando utilitários com Hoogle e Hayoo: MissingH, extra

por Franklin Chen

traduzido por Pedro Yamada

Esse é um artigo escrito por Franklin Chen e traduzido para o português. Ler original.

Índice de toda a série

O índice de toda a série está no topo do artigo para o dia 1.

Dia 6

Nunca será o caso que tudo que todos consideram “útil” vai estar na “biblioteca padrão” de qualquer ecossistema de uma linguagem. Uma das funcionalidades mais legais do ecossistema do Haskell (que impressiona todos os não-Haskellers a quem eu a mostro) é a habilidade de pesquisar por funções pela sua assinatura de tipo, usando o Hoogle ou o Hayoo. Você também pode usar outros critérios, como nomes; isso pode ser útil se você tiver um chute do nome de alguma função que precise.

Parecem haver duas filosofias para bibliotecas de utilitários:

Eu tendo a preferir o re-uso, mas já copiei (e até modifiquei) trechos de código que precisava, só porque não queria o resto de uma biblioteca enorme que depende de mais outras coisas que não preciso. Eu acho que esse é um problema de granularidade. Já que temos a Web hoje, muitas pessoas propuseram a ideia de que o conceito de uma “biblioteca” deveria virar obsoleto dando lugar a “micro-bibliotecas”, talvez até no nível de um único indentificador exportado, mas o tópico está fora do escopo desse artigo. (Para uma dessas ideias, veja o “The internet of code” do Gabriel Gonzalez).

[N.T. A HaskellBR pode contribuir com isso! Ajude em haskellbr/missingh]

A situação fica complicada pelo fato de que frequentemente, podemos reinventar um monte de coisas com só algumas linhas de Haskell, então porque se importar em procurar pela implementação de outros no primeiro lugar?

De qualquer forma, vamos assumir que o propósito desse artigo é que você está interessado em encontrar e usar bibliotecas de utilitários. Eu vou mostrar como encontrar algumas funções de exemplo e chegar em duas bibliotecas de utilitários que uso, espertamente e informativamente chamadas de MissingH e extra.


Um exemplo de listas/strings

Algum tempo atrás estava manipulando strings (eu tinha uma String, não um Text ou uma ByteString) e precisava substituir todas as ocorrências de uma substring em um path com outra substring. Por exemplo, como um teste do HSpec para uma função hipotética chamada replace:

  it "replaces all substrings within a string" $ do
    replace "abc" "d" "123abc123abc" `shouldBe` "123d123d"

Claro, não seria difícil escrever o código para fazer isso, mas porque não procurar por algo pronto que eu possa usar?

Por qual assinatura deveríamos procurar? Talvez:

String -> String -> String -> String

Ou seja (com algo próximo de sua documentação Haddock):

String     -- ^ a substring para substituir
-> String  -- ^ um substituto da substring
-> String  -- ^ string original
-> String  -- ^ string resultado

Ok, vamos tentar essa assinatura como uma busca no Hayoo. Hmm, os resultados não são promissores. No topo está alguma coisa extranha e não documentada sobre regexes, que provavelmente não é o que queremos.

Uma técnica de busca importante: assuma o menor número de coisas possível

Provavelmente a dica mais importante para conseguir resultados bons para uma busca a partir de um tipo é fazer o tipo tão genérico quanto possível: quanto mais variáveis de tipo, o melhor, e também só use constraints de type-class que você precisar de fato. A operação que nós queremos na verdade não é restrita a strings. Na verdade, é uma operação sobre listas. Então o tipo que nós queremos é:

Eq a => [a] -> [a] -> [a] -> [a]

Essa função utilitária assume o menor número coisas necessárias para fazer o trabalho para Strings, já que String é só um nome para [Char] e Char é uma instância da type-class Eq. Para o propósito de substituir sub-listas, nós não nos importamos se comparamos characteres: só importa que qualquer que seja o tipo dos elementos nas listas e sub-listas possa ser comparado por igualdade.

A busca no Hayoo imediatamente retorna resultados mais promissores do que a antiga com String, com pacotes como utility-ht, MissingH e extra. O pandoc também apareceu, mas ele é uma ferramenta enorme de processamento de texto, não uma biblioteca que eu incluiria só por uma função utilitária pequena! (Note que pandoc foi coberto em um dia de Hackage de 2013.

A busca no Hoogle em hoogle.haskell.org funciona bem também. (Note que a busca do Hoogle no site antigo dá resultados ruins.)

Modificando nossos testes para checar a função genérica

Eu passei brevemente por refatorar testes do HSpec no dia 3. Aqui está como testar muitas implementações da mesma função (vamos ir com MissingH e extra), e também testar replace com tipos de input diferentes: tanto String (que não passa de [Char]) e [Int]:

module MissingHExtraExampleSpec where

-- | Do MissingH
import qualified Data.List.Utils as ListUtils

-- | Do extra
import qualified Data.List.Extra as ListExtra

import Test.Hspec (Spec, hspec, describe, it, shouldBe)

-- | Necessário para a descoberta automática
spec :: Spec
spec =
  describe "MissingH and extra" $ do
    describeReplace "MissingH" ListUtils.replace
    describeReplace "extra" ListExtra.replace

Mas o código não compila! Por que?

-- | Isso não compila...
describeReplace
  :: String
  -- ^ A descrição
  -> (Eq a => [a] -> [a] -> [a] -> [a])
  -- ^ A implementação de replace
  -> Spec
describeReplace description replace =
  describe description $ do
    it "replaces all substrings within a string" $ do
      replace "abc" "d" "123abc123abc" `shouldBe` "123d123d"
    it "replaces all int sublists within an int list" $ do
      replace [0 :: Int, 1] [100, 101, 102] [0, 1, 2, 3, 0, 0, 1, 4]
        `shouldBe` [100, 101, 102, 2, 3, 0, 100, 101, 102, 4]

Uma nota no uso de higher-rank types para refatorar

O erro é útil se você sabe o que está acontecendo, mas não muito caso contrário. Sim, nós precisamos de higher-rank types.

    Illegal polymorphic or qualified type:
      Eq a => [a] -> [a] -> [a] -> [a]
    Perhaps you intended to use RankNTypes or Rank2Types
    In the type signature for ‘describeReplace’:
      describeReplace :: String
                         -> (Eq a => [a] -> [a] -> [a] -> [a]) -> Spec

Higher-rank types são uma extensão ao GHC discutida em um Dia de Extensões do GHC 2014. Higher-rank types são muito úteis e uma funcionalidade faltando nos sistemas de tipos da maior parte das outras linguagens.

Resumidamente, a função replace que queremos tem tipo:

forall a. Eq a => [a] -> [a] -> [a] -> [a]

aqui nós quantificamos a variável de tipo a para que o constraint Eq se aplique dentro do seu escopo. Leia o tipo como “para todos os tipos a onde a é um membro da type-class Eq, [a] -> [a] -> [a] -> [a]”. Haskell normal sem a extensão, não permite usar esse tipo como um parâmetro para outra função, porque ele não tem um forall explícito e insere um forall para você, implicitamente, no escopo do topo da assinatura, o que é o escopo incorreto para o que queremos.

Então precisamos adicionar a diretiva e mudar o tipo do parâmetro "A implementação de replace" para ter quantificação explícita e está tudo ok:

{-# LANGUAGE RankNTypes #-}

describeReplace
  :: String  -- ^ description
  -> (forall a. Eq a => [a] -> [a] -> [a] -> [a])  -- ^ replace
  -> Spec

Uma nota sobre quantificação implícita em Haskell e linguagens relacionadas

Eu gostaria que a quantificação de variáveis de tipo fosse explíta em Haskell, que anotações forall fossem necessárias, como o PureScript faz, porque entender quantificação de variáveis de tipo é importante para entender completamente o que acontece a nível dos tipos em linguagens como ML e Haskell.

Por exemplo, esse um bom artigo sobre como entender a restrição de valores e a restrição de monomorfismo, que pode ser desafiador se você não tem um modelo mental do que acontece nos bastidores.

O prazer de explorar bibliotecas

Uma coisa que pode acontecer se você encontrar uma função utilitária útil, é que você pode explorar o módulo que a contem ou a biblioteca inteira, só procurando por mais coisas que podem ser úteis no futuro. Por exemplo, eu acho a biblioteca do Neil Mitchell extra agradável o suficiente (bons nomes, boa documentação no Hackage) que eu a uso quando posso e recomendo dar uma olhada nela. O repositório no GitHub do MissingH sugere que ela não está mais sendo mantida, então eu estou tentando evitar seu uso.

No mundo de livros e revistas físicas, eu ainda vou até a livraria e fico olhando as prateleiras de livros/revistas/DVDs novos e acabo olhando na prateleira de algum item que eu tenha encontrado para ver se há alguma outra coisa relacionada que eu possa querer conferir.

Cavando mais fundo

Note que se você está na busca de bibliotecas potencialmente úteis, mas sem necessidade imediata, você também pode as encontrar só olhando para a lista de dependências de bibliotecas populares. Eu confesso que as vezes eu me perco clicando nas dependências de uma página do Hackage. Se você ficar clicando nas dependências do pacote lens de Edward Kmett vai acabar encontrando um número impressinante de bibliotecas úteis, porque ele é um mestre do universo de re-uso de código.

A analogia com livros ou papel existe aqui, claro, e é olhar nas referências ou bibliografia de algo para encontrar mais coisas para ler.

Conclusão

Para o dia 6, dei um exemplo de como buscar por uma função no Hoogle ou no Hayoo e passei um pouco por como generalizar assinaturas propriamente. Eu recomendo o uso da biblioteca de utilitários extra.

Todo o código

Todo o código para a série estará nesse repositório do GitHub.


Nota do tradutor

Se você quer ajudar com esse tipo de coisa, agora é a hora. Entre no Slack ou no IRC da HaskellBR e contribua. Esse blog e outros projetos associados estão na organização haskellbr no GitHub e em haskellbr.com/git.

Share Comente no Twitter