La semántica de Haskell usa un "valor inferior" para analizar el significado del código de Haskell. No es algo que realmente uses directamente en la programación de Haskell, y regresar None
no es para nada el mismo tipo de cosas.
El valor inferior es el valor atribuido por la semántica de Haskell a cualquier cálculo que no puede evaluar un valor normalmente. ¡Una de las formas en que un cálculo de Haskell puede hacer eso es en realidad lanzando una excepción! Entonces, si intentabas usar este estilo en Python, en realidad solo deberías lanzar excepciones de manera normal.
La semántica de Haskell usa el valor inferior porque Haskell es vago; puede manipular los "valores" que devuelven los cálculos que aún no se han ejecutado. Puede pasarlos a funciones, pegarlos en estructuras de datos, etc. Tal cálculo no evaluado podría generar una excepción o bucle para siempre, pero si nunca necesitamos examinar el valor, entonces el cálculo nuncaejecutar y encontrar el error, y nuestro programa en general podría lograr hacer algo bien definido y finalizar. Entonces, sin querer explicar qué significa el código de Haskell al especificar el comportamiento operativo exacto del programa en tiempo de ejecución, en su lugar declaramos que estos cálculos erróneos producen el valor inferior y explicamos qué se comporta ese valor; Básicamente, cualquier expresión que deba depender de cualquier propiedad del valor inferior (aparte de la existente) también dará como resultado el valor inferior.
Para permanecer "puro", todas las formas posibles de generar el valor inferior deben tratarse como equivalentes. Eso incluye el "valor inferior" que representa un bucle infinito. Como no hay forma de saber que algunos bucles infinitos en realidad son infinitos (podrían terminar si los ejecuta un poco más), no puede examinar ninguna propiedad de un valor inferior. No puede probar si algo está en la parte inferior, no puede compararlo con nada más, no puede convertirlo en una cadena, nada. Todo lo que puede hacer con uno es colocarlo (parámetros de función, parte de una estructura de datos, etc.) intactos y sin examinar.
Python ya tiene este tipo de fondo; Es el "valor" que se obtiene de una expresión que arroja una excepción o no termina. Debido a que Python es estricto en lugar de perezoso, tales "fondos" no se pueden almacenar en ningún lugar y, potencialmente, no se pueden examinar. Por lo tanto, no hay necesidad real de utilizar el concepto del valor inferior para explicar cómo los cálculos que no devuelven un valor aún pueden tratarse como si tuvieran un valor. Pero tampoco hay ninguna razón por la que no pueda pensar de esta manera acerca de las excepciones si lo desea.
Lanzar excepciones se considera realmente "puro". Es la captura de excepciones que se rompe la pureza - precisamente porque le permite inspeccionar algo acerca de ciertos valores inferiores, en lugar de tratar a todos indistintamente. En Haskell solo puede detectar excepciones IO
que permiten una interfaz impura (por lo que generalmente ocurre en una capa bastante externa). Python no exige la pureza, pero aún puede decidir por sí mismo qué funciones forman parte de su "capa impura externa" en lugar de las funciones puras, y solo se permite detectar excepciones allí.
Volver en su None
lugar es completamente diferente. None
es un valor no inferior; puede probar si algo es igual a él, y la persona que llama de la función que regresó None
continuará ejecutándose, posiblemente usando el None
inapropiado.
Entonces, si estaba pensando en lanzar una excepción y quiere "volver al fondo" para emular el enfoque de Haskell, simplemente no haga nada en absoluto. Deje que la excepción se propague. Eso es exactamente lo que los programadores de Haskell quieren decir cuando hablan de una función que devuelve un valor inferior.
Pero eso no es lo que los programadores funcionales quieren decir cuando dicen evitar las excepciones. Los programadores funcionales prefieren "funciones totales". Estos siempre devuelven un valor no inferior válido de su tipo de retorno para cada entrada posible. Entonces, cualquier función que pueda generar una excepción no es una función total.
La razón por la que nos gustan las funciones totales es que son mucho más fáciles de tratar como "cajas negras" cuando las combinamos y manipulamos. Si tengo una función total que devuelve algo de tipo A y una función total que acepta algo de tipo A, entonces puedo llamar a la segunda en la salida de la primera, sin saber nada sobre la implementación de cualquiera; Sé que obtendré un resultado válido, sin importar cómo se actualice el código de cualquiera de las funciones en el futuro (siempre que se mantenga su totalidad y siempre que conserven la misma firma de tipo). Esta separación de preocupaciones puede ser una ayuda extremadamente poderosa para la refactorización.
También es algo necesario para funciones confiables de orden superior (funciones que manipulan otras funciones). Si quiero escribir código que recibe una función completamente arbitraria (con una interfaz conocida) como un parámetro que tengo que tratarlo como un cuadro negro porque no tengo manera de saber qué entradas podrían dar lugar a un error. Si me dan una función total, ninguna entrada causará un error. Del mismo modo, la persona que llama de mi función de orden superior no sabrá exactamente qué argumentos uso para llamar a la función que me pasan (a menos que quieran depender de los detalles de mi implementación), por lo que pasar una función total significa que no tienen que preocuparse lo que hago con eso
Por lo tanto, un programador funcional que le aconseje que evite las excepciones preferiría que en su lugar devuelva un valor que codifique el error o un valor válido, y que requiera que para usarlo esté preparado para manejar ambas posibilidades. Cosas como Either
tipos o Maybe
/ Option
tipos son algunos de los más sencillos se aproxima a ello en los idiomas más inflexible de tipos (se usa generalmente sintaxis especial o funciones de orden superior a las cosas juntos ayuda de pegamento que necesitan una A
de las cosas que producen una Maybe<A>
).
Una función que devuelve None
(si ocurrió un error) o algún valor (si no hubo error) no está siguiendo ninguna de las estrategias anteriores.
En Python con pato escribiendo, el estilo Either / Maybe no se usa mucho, en lugar de permitir que se generen excepciones, con pruebas para validar que el código funciona en lugar de confiar en que las funciones sean totales y combinables automáticamente según sus tipos. Python no tiene facilidad para hacer cumplir ese código que usa cosas como los tipos tal vez correctamente; incluso si lo estaba utilizando como una cuestión de disciplina, necesita pruebas para ejercer realmente su código para validar eso. Por lo tanto, el enfoque de excepción / fondo probablemente sea más adecuado para la programación funcional pura en Python.