A semântica de Haskell usa um "valor inferior" para analisar o significado do código Haskell. Não é algo que você realmente use diretamente na programação do Haskell, e retornar None
não é o mesmo tipo de coisa.
O valor inferior é o valor atribuído pela semântica de Haskell a qualquer cálculo que falha ao avaliar normalmente um valor. Uma dessas maneiras que uma computação Haskell pode fazer é lançando uma exceção! Portanto, se você estava tentando usar esse estilo no Python, deveria lançar exceções normalmente.
A semântica de Haskell usa o valor mais baixo porque Haskell é preguiçoso; você pode manipular "valores" retornados por cálculos que ainda não foram executados. Você pode passá-los para funções, colá-los em estruturas de dados, etc. Essa computação não avaliada pode gerar uma exceção ou um loop para sempre, mas se nunca precisarmos realmente examinar o valor, a computação nuncaexecute e encontre o erro, e nosso programa geral poderá fazer algo bem definido e finalizado. Portanto, sem querer explicar o que significa o código Haskell, especificando o comportamento operacional exato do programa em tempo de execução, declaramos que tais cálculos errôneos produzem o valor mais baixo e explicamos o que esse valor se comporta; basicamente, qualquer expressão que precise depender de todas as propriedades do valor inferior (além da existente) também resultará no valor inferior.
Para permanecer "puro", todas as formas possíveis de gerar o valor mais baixo devem ser tratadas como equivalentes. Isso inclui o "valor inferior" que representa um loop infinito. Como não há como saber que alguns loops infinitos são realmente infinitos (eles podem terminar se você os executar por mais algum tempo), não é possível examinar nenhuma propriedade de valor inferior. Você não pode testar se algo está embaixo, não pode compará-lo a qualquer outra coisa, não pode convertê-lo em uma string, nada. Tudo o que você pode fazer com um é colocar os lugares (parâmetros de função, parte de uma estrutura de dados, etc.) intocados e não examinados.
Python já tem esse tipo de fundo; é o "valor" que você obtém de uma expressão que gera uma exceção ou não termina. Como o Python é mais rigoroso do que preguiçoso, esses "fundos" não podem ser armazenados em nenhum lugar e potencialmente deixados sem serem examinados. Portanto, não há necessidade real de usar o conceito de valor inferior para explicar como os cálculos que não retornam um valor ainda podem ser tratados como se tivessem um valor. Mas também não há razão para que você não pense dessa maneira sobre exceções, se quiser.
Lançar exceções é realmente considerado "puro". É a captura de exceções que quebra a pureza - precisamente porque permite que você inspecione algo sobre certos valores inferiores, em vez de tratá-los todos de forma intercambiável. No Haskell, você só pode capturar exceções no IO
que permite interfaces impuras (o que geralmente acontece em uma camada bastante externa). O Python não impõe pureza, mas você ainda pode decidir por si mesmo quais funções fazem parte da sua "camada impura externa" em vez de funções puras, e apenas se permite capturar exceções lá.
Retornar None
é completamente diferente. None
é um valor não inferior; você pode testar se algo é igual a ele e o chamador da função que retornou None
continuará sendo executado, possivelmente usando o None
inapropriadamente.
Portanto, se você estava pensando em lançar uma exceção e deseja "voltar ao fundo" para imitar a abordagem de Haskell, você simplesmente não faz nada. Deixe a exceção se propagar. É exatamente isso que os programadores Haskell querem dizer quando falam sobre uma função retornando um valor mais baixo.
Mas não é isso que os programadores funcionais querem dizer quando dizem para evitar exceções. Programadores funcionais preferem "funções totais". Eles sempre retornam um valor não inferior válido de seu tipo de retorno para todas as entradas possíveis. Portanto, qualquer função que possa gerar uma exceção não é uma função total.
A razão pela qual gostamos de funções totais é que elas são muito mais fáceis de tratar como "caixas pretas" quando as combinamos e manipulamos. Se eu tiver uma função total retornando algo do tipo A e uma função total que aceite algo do tipo A, então eu posso chamar o segundo na saída do primeiro, sem saber nada sobre a implementação de qualquer um; Eu sei que vou obter um resultado válido, não importa como o código de uma das funções seja atualizado no futuro (desde que a totalidade seja mantida e enquanto eles mantêm a mesma assinatura de tipo). Essa separação de preocupações pode ser uma ajuda extremamente poderosa para a refatoração.
Também é um pouco necessário para funções confiáveis de ordem superior (funções que manipulam outras funções). Se eu quiser escrever código que recebe uma função completamente arbitrário (com uma interface conhecida) como um parâmetro que eu tenho de tratá-lo como uma caixa preta, porque eu não tenho nenhuma maneira de saber quais entradas podem desencadear um erro. Se eu receber uma função total, nenhuma entrada causará um erro. Da mesma forma, o responsável pela chamada da minha função de ordem superior não saberá exatamente quais argumentos eu uso para chamar a função que eles passam por mim (a menos que desejem depender dos detalhes da minha implementação). Portanto, passar uma função total significa que eles não precisam se preocupar com o que eu faço com isso.
Portanto, um programador funcional que o aconselha a evitar exceções prefere que você retorne um valor que codifique o erro ou um valor válido e exija que, para usá-lo, esteja preparado para lidar com as duas possibilidades. Coisas como Either
types ou Maybe
/ Option
types são algumas das abordagens mais simples para fazer isso em linguagens mais fortemente tipadas (geralmente usadas com sintaxe especial ou funções de ordem superior para ajudar a colar coisas que precisam de um A
com coisas que produzem a Maybe<A>
).
Uma função que retorna None
(se ocorreu um erro) ou algum valor (se não houve erro) está seguindo nenhuma das estratégias acima.
No Python com duck digitando, o estilo Either / Maybe não é muito usado, ao invés disso, permite que exceções sejam lançadas, com testes para validar que o código funciona, em vez de confiar que as funções sejam totais e combináveis automaticamente com base em seus tipos. O Python não tem nenhuma facilidade para impor que o código use coisas como Talvez tipos corretamente; mesmo se você o estivesse usando por uma questão de disciplina, precisará de testes para realmente exercitar seu código para validar isso. Portanto, a abordagem de exceção / parte inferior é provavelmente mais adequada à programação funcional pura em Python.