Home

24 dias de Hackage, 2015 - dia 16 - safe; o que é segurança mesmo?

por Franklin Chen

traduzido por Pedro Yamada

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

Encontro HaskellBR São Paulo

Vamos nos encontrar em São Paulo em 25 de Janeiro de 2016. Marque sua presença e comente se não puder vir.

Índice de toda a série

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

Dia 16

(Discussão no Reddit do original)

Hoje farei algo estranho: até agora, discuti bibliotecas e ferramentas que eu de fato uso. Hoje, vou discutir uma biblioteca que não uso e pensar um pouco sobre porque não a uso e porque poderia querer a usar. Essa biblioteca é o safe, que tenta nos proteger da infeliz “insegurança” de funções comuns do Prelude padrão.

Isso é uma coisa boa, certo? Afinal, no dia 7, eu promovi o uso da lista NonEmpty e de novo a usei no dia 14. Eu gosto de segurança, mas o que é “segurança” mesmo?

Segurança é relativa

A noção de segurança é sempre relativa a algum critério, alguma expectativa e, de forma mais geral, no contexto de algum estilo de vida. E estilos de vida sempre podem ser protegidos por mecânismos diferentes, desde a proteção estríta até guias de estilo ou contratos sociais implícitos e ostracismo.

No contexto do pacote safe, o tipo de segurança sobre o qual estamos preocupados é nunca ver um programa quebrar com uma exceção vinda de código puro como:

*** Exception: Prelude.head: empty list

Isso acontece se você chama head em uma lista vazia:

-- | Crashes if nobody in line.
unsafeReportFirstInLine :: [Int] -> String
unsafeReportFirstInLine nums =
  "next up: customer " ++ show (head nums)
> unsafeReportFirstInLine []
"next up: customer *** Exception: Prelude.head: empty list

A solução segura é nunca chame head em uma lista que pode estar vazia. Há formas diferentes de garantir isso.

Resolvendo um problema da psicologia humana?

Uma solução é mudar o tipo de head.

Podemos dizer que funções como head no Prelude do Haskell são um erro histórico de 1990 que continou na linguagem, porque ela encoraja programadores (principalmente recem-chegados) a chamar head. Se for fácil fazer coisas inseguras, as pessoas vão certamente as fazer e provavelmente as fazer frequentemente.

Comunidades de linguagens mais novas atacam o problema psicológico não fornecendo uma função insegura head; por exemplo, o ecossistema padrão do PureScript fornece um seguro Data.List.head com tipo forall a. List a -> Maybe a, mas também fornece um módulo Data.List.Unsafe que incluí Data.List.Unsafe.head com tipo forall a. List a -> a e mais.

E o módulo List do Elm fornece List.head com tipo List a -> Maybe a.

Marcando algo como inseguro pelo menos permite ao escritor e leitor do código a tomarem nota de que algo pode dar errado, então eu acho que esse é um bom começo para uma solução. Além disso, a pesquisa de psicologia tem mostrado claramente que padrões importam: se o objetivo é promover segurança, é melhor que o padrão seja seguro, e desabilitar ele explicitamente para ser inseguro, ao invés do contrário.

Infelizmente, por razões históricas, se você está trabalhando com listas ou algumas outras estruturas de dados em Haskell, você é forçado a ter que escolher explicitamente ser seguro, já que o padrão é inseguro.

O pacote safe nos deixa ser seguros de forma mais fácil.

Você ganha funções como Safe.headMay com tipo [a] -> Maybe a.

-- | Using 'Safe.headMay' and pattern matching on Maybe.
reportFirstInLine :: [Int] -> String
reportFirstInLine nums =
  case Safe.headMay nums of
    Just num -> "next up: customer " ++ show num
    Nothing -> "there are no customers in line"

Uma alternativa: fazendo pattern-matching diretamente na estrutura de dados

Na prática, eu não uso funções como headMay, porque eu só faço pattern-matching na própria lista:

-- | Using pattern matching on list.
reportFirstInLine2 :: [Int] -> String
reportFirstInLine2 [] = "there are no customers in line"
reportFirstInLine2 (num:_) = "next up: customer " ++ show num

Quando eu penso a respeito, no entanto, há algo que cheira mal nessa solução. O wildcard _ no pattern mostra que nós estamos extraindo mais informação do que precisamos de fato. A princípio, nós poderíamos só extrair o que precisamos e headMay faz exatamente isso. Eu estou basicamente violando o encapsulamento conceitual extraindo e ignorando mais do que eu preciso (o resto da lista).

Então acredito que eu de fato deveria começar a usar o headMay nesse tipo de contexto e pensando mais profundamente, acho que o único motivo que esse ainda não é o caso é que o Prelude padrão não o incluí! Era mais fácil fazer pattern-matching do que encontrar o safe e o adicionar como uma dependência.

Quantos de vocês pensam como eu e usariam o headMay se ele fizesse parte do Prelude, mas já que ele não faz, usam um pattern match na lista?

Revisitando a solução headMay

Alguns de vocês podem gostar de usar a função maybe que tem tipo b -> (a -> b) -> Maybe a -> b, para evitar fazer pattern-matching no Maybe:

-- | No pattern matching.
reportFirstInLine3 :: [Int] -> String
reportFirstInLine3 =
  maybe "there are no customers in line" reportOne . Safe.headMay

reportOne :: Int -> String
reportOne num = "next up: customer " ++ show num

Também há uma versão “code-golf” que eu não recomendo:

-- | Code golf.
reportFirstInLine4 :: [Int] -> String
reportFirstInLine4 =
  maybe "there are no customers in line"
        (("next up: customer " ++) . show)
        . Safe.headMay

Outras boas coisas no safe

Cada uma das funções ...May também tem outras variações úteis.

Uma delas permite especificar um padrão para retornar no caso de que lista esteja vazia:

headDef :: a -> [a] -> a

Outra é insegura, mas pelo menos gera uma exceção melhor. Isso é útil se você sabe que uma lista não está vazia, e você não quer ter que lidar com o caso em que ela está vazia, mas por via das dúvidas, gerar uma exceção que se atirada, pelo menos te diz de onde o seu “erro interno fatal” veio.

headNote :: String -> [a] -> a

Ser exato é um problema de segurança

O módulo Safe.Exact fornece uma série de funções úteis que têm a ver com acessar elementos arbitrários de uma lista e checar o tamanho de listas. Aqui, “segurança” não mais se refere a uma exceção. Ela se refere a algo mais sútil: o código que você escreve que dá type-check e roda, mas faz algo inesperado. Por exemplo, é fácil usar o take de alguma forma que você não queira, porque ele permite silenciosamente que você “pegue” mais elementos da lista do que ela contém, mas só assume que sabe o que está fazendo e não quer dizer “pegue 1000 elementos” mas “pegue 1000 elementos e se não tiverem 1000 elementos, pegue todos os elementos”. Eu já fui mordido pelo take antes, em uma ocasião em que passei um número absurdo que eu não queria. Então a família de funções Safe.Exact.takeExact... é muito útil. No passado, antes de descobrir sobre o safe, eu basicamente escrevia meus próprios wrappers e agora não vou ter mais que fazer isso.

Foldable

Finalmente o módulo Safe.Foldable é útil porque o Foldable é cheio de operações inseguras.

Quando você sabe alguma coisa sobre seus dados que o tipo não sabe

Uma nota final sobre tratar a ideia de ser exato em operações sobre listas como um problema de segurança: a solução embasada à não ser exato quando se trata de acessar um elemento ou o tamanho de listas é transformar os bugs em potencial em erros de tipo, usando tipos dependentes; um tipo de lista que é dependente no seu tamanho. Um dia de Hackage posterior vai mostrar soluções.

Conclusão

O pacote safe é uma boa biblioteca utilitária que amarra as operações inseguras do Prelude. Há razões técnicas e psicológias pelas quais eu ainda não a usei, e eu discuti elas, mas vou a usar no futuro quando ela se encaixar nas minhas necessidades.

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.

Há um milestone no GitHub com tarefas esperando por você..

Share Comente no Twitter