¿Cuál es el beneficio de no tener "excepciones de tiempo de ejecución", como afirma Elm?

14

Algunos idiomas afirman no tener "excepciones de tiempo de ejecución" como una clara ventaja sobre otros idiomas que las tienen.

Estoy confundido al respecto.

La excepción de tiempo de ejecución es solo una herramienta, que yo sepa, y cuando se usa bien:

  • puede comunicar estados "sucios" (lanzar datos inesperados)
  • agregando pila puedes apuntar a la cadena de error
  • puede distinguir entre el desorden (por ejemplo, devolver un valor vacío en una entrada no válida) y el uso inseguro que requiere atención de un desarrollador (por ejemplo, lanzar una excepción en una entrada no válida)
  • puede agregar detalles a su error con el mensaje de excepción que proporciona más detalles útiles que ayudan a los esfuerzos de depuración (teóricamente)

Por otro lado, me resulta muy difícil depurar un software que "traga" las excepciones. Por ejemplo,

try { 
  myFailingCode(); 
} catch {
  // no logs, no crashes, just a dirty state
}

Entonces, la pregunta es: ¿cuál es la fuerte ventaja teórica de no tener "excepciones de tiempo de ejecución"?

Ejemplo

enlace

  

No hay errores de ejecución en la práctica. No nulo No indefinido no es una función.

    
pregunta atoth 01.12.2016 - 17:43

5 respuestas

26

Las excepciones tienen una semántica extremadamente limitante. Deben manejarse exactamente donde se lanzan, o en la pila de llamadas directas hacia arriba, y no hay ninguna indicación para el programador en el momento de la compilación si olvida hacerlo.

Contrasta esto con Elm, donde los errores se codifican como Results o Maybes , que son ambos valores . Eso significa que obtienes un error de compilación si no manejas el error. Puede almacenarlos en una variable o incluso en una colección para diferir su manejo a un horario conveniente. Puede crear una función para manejar los errores de una manera específica de la aplicación en lugar de repetir bloques try-catch muy similares en todo el lugar. Puede encadenarlos en un cálculo que tenga éxito solo si todas sus partes tienen éxito, y no es necesario que estén agrupados en un solo bloque de prueba. No estás limitado por la sintaxis incorporada.

Esto no es nada como "tragar excepciones". Está explicando las condiciones de error en el sistema de tipos y proporcionando semánticas alternativas mucho más flexibles para manejarlas.

Considere el siguiente ejemplo. Puede pegar esto en enlace si desea verlo en acción.

import Html exposing (Html, Attribute, beginnerProgram, text, div, input)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import String

main =
  beginnerProgram { model = "", view = view, update = update }

-- UPDATE

type Msg = NewContent String

update (NewContent content) oldContent =
  content

getDefault = Result.withDefault "Please enter an integer" 

double = Result.map (\x -> x*2)

calculate = String.toInt >> double >> Result.map toString >> getDefault

-- VIEW

view content =
  div []
    [ input [ placeholder "Number to double", onInput NewContent, myStyle ] []
    , div [ myStyle ] [ text (calculate content) ]
    ]

myStyle =
  style
    [ ("width", "100%")
    , ("height", "40px")
    , ("padding", "10px 0")
    , ("font-size", "2em")
    , ("text-align", "center")
    ]

Tenga en cuenta que String.toInt en la función calculate tiene la posibilidad de fallar. En Java, esto tiene el potencial de lanzar una excepción de tiempo de ejecución. A medida que lee la entrada del usuario, tiene una probabilidad bastante buena de hacerlo. En cambio, Elm me obliga a lidiar con él devolviendo Result , pero observa que no tengo que lidiar con eso de inmediato. Puedo duplicar la entrada y convertirla en una cadena, luego verifique si hay una entrada incorrecta en la función getDefault . Este lugar es mucho más adecuado para la verificación que el punto donde ocurrió el error o hacia arriba en la pila de llamadas.

La forma en que el compilador fuerza nuestra mano también es mucho más detallada que las excepciones comprobadas de Java. Debe usar una función muy específica como Result.withDefault para extraer el valor que desea. Si bien técnicamente puedes abusar de ese tipo de mecanismo, no tiene mucho sentido. Ya que puede aplazar la decisión hasta que sepa cuál es un buen mensaje de error / predeterminado, no hay razón para no usarla.

    
respondido por el Karl Bielefeldt 01.12.2016 - 20:45
10

Para entender esta afirmación, primero debemos entender qué nos compra un sistema de tipo estático. En esencia, lo que nos ofrece un sistema de tipo estático es una garantía: iff el tipo de programa verifica, no puede ocurrir una cierta clase de comportamientos de tiempo de ejecución.

Eso suena ominoso. Bueno, un verificador de tipos es similar a un verificador de teoremas. (En realidad, según el Curry-Howard-Isomorfismo, son lo mismo.) Una cosa que es muy peculiar acerca de los teoremas es que cuando uno prueba un teorema, prueba exactamente lo que dice el teorema, nada más. (Eso es, por ejemplo, por qué, cuando alguien dice "He comprobado que este programa es correcto", siempre debe preguntar "defina 'correcto'".) Lo mismo es cierto para los sistemas tipográficos. Cuando decimos "un programa es de tipo seguro", lo que queremos decir es no que no puede ocurrir ningún error posible. Solo podemos decir que los errores que el sistema de tipos nos promete evitar no pueden ocurrir.

Por lo tanto, los programas pueden tener infinitos comportamientos de ejecución diferentes. De ellos, infinitos son útiles, pero también infinitos son "incorrectos" (para varias definiciones de "corrección"). Un sistema de tipo estático nos permite probar que no se puede producir un determinado conjunto fijo finito de esos infinitos comportamientos incorrectos en tiempo de ejecución.

La diferencia entre los diferentes sistemas de tipos radica básicamente en qué, cuántos y en qué tan complejos comportamientos de tiempo de ejecución se puede demostrar que no ocurren. Los sistemas tipográficos débiles como los de Java solo pueden probar cosas muy básicas. Por ejemplo, Java puede probar que un método que se escribe como devolver un String no puede devolver un List . Pero, por ejemplo, puede no demostrar que el método no regresará. Tampoco puede probar que el método no arrojará una excepción. Y no puede probar que no devolverá el error String ; cualquier String satisfará el comprobador de tipos. (Y, por supuesto, incluso null también lo satisfará.) Incluso hay cosas muy simples que Java no puede probar, por lo que tenemos excepciones como ArrayStoreException , ClassCastException , o el favorito de todos, el NullPointerException .

Los sistemas tipográficos más potentes, como el de Agda, también pueden probar cosas como "devolverá la suma de los dos argumentos" o "devuelve la versión ordenada de la lista pasada como un argumento".

Ahora, lo que los diseñadores de Elm quieren decir con la afirmación de que no tienen excepciones de tiempo de ejecución es que el sistema de tipos de Elm puede probar la ausencia de (una parte significativa de) los comportamientos de tiempo de ejecución que en otros idiomas no pueden ni se ha comprobado que no se produce y, por lo tanto, puede dar lugar a un comportamiento erróneo en el tiempo de ejecución (lo que en el mejor de los casos significa una excepción, en el peor de los casos significa un fallo y, en el peor de los casos, no hay bloqueo, no hay excepción y solo resultado incorrectamente silencioso).

Entonces, ellos no dicen "no implementamos excepciones". Dicen que "las cosas que serían excepciones en tiempo de ejecución en los lenguajes típicos con los que los programadores típicos que vienen a Elm tendrían experiencia, están atrapadas por el sistema de tipos". Por supuesto, alguien que venga de Idris, Agda, Guru, Epigram, Isabelle / HOL, Coq o idiomas similares verá a Elm como un débil débil en comparación. La declaración está más dirigida a los programadores típicos de Java, C♯, C ++, Objective-C, PHP, ECMAScript, Python, Ruby, Perl, ...

    
respondido por el Jörg W Mittag 02.12.2016 - 02:43
4

Elm no puede garantizar ninguna excepción en tiempo de ejecución por la misma razón que C puede garantizar ninguna excepción en tiempo de ejecución: el lenguaje no admite el concepto de excepciones.

Elm tiene una forma de señalar las condiciones de error en el tiempo de ejecución, pero este sistema no es una excepción, es "Resultados". Una función que puede fallar devuelve un "Resultado" que contiene un valor regular o un error. Elms está fuertemente tipado, por lo que esto es explícito en el sistema de tipos. Si una función siempre devuelve un entero, tiene el tipo Int . Pero si devuelve un entero o falla, el tipo de retorno es Result Error Int . (La cadena es el mensaje de error). Esto lo obliga a manejar explícitamente ambos casos en el sitio de la llamada.

Aquí hay un ejemplo de la introducción (un poco simplificado):

view : String -> String 
view userInputAge =
  case String.toInt userInputAge of
    Err msg ->
        text "Not a valid number!"

    Ok age ->
        text "OK!"

La función toInt puede fallar si la entrada no es analizable, por lo que su tipo de retorno es Result String int . Para obtener el valor entero real, debe "desempaquetar" mediante la coincidencia de patrones, lo que a su vez lo obliga a manejar ambos casos.

Los resultados y las excepciones hacen fundamentalmente lo mismo, la diferencia importante es que los "valores predeterminados". Las excepciones aumentarán y terminarán el programa de manera predeterminada, y usted debe capturarlas explícitamente si desea manejarlas. El resultado es a la inversa: usted está obligado a manejarlos de manera predeterminada, por lo que tiene que pasarlos explícitamente hasta la parte superior si desea que finalicen el programa. Es fácil ver cómo este comportamiento puede llevar a un código más robusto.

    
respondido por el JacquesB 01.12.2016 - 18:27
3

Primero, tenga en cuenta que su ejemplo de excepciones "deglución" es en general una práctica terrible y no tiene relación alguna con no tener excepciones de tiempo de ejecución; cuando piensas en ello, tuviste un error de tiempo de ejecución, pero elegiste ocultarlo y no hacer nada al respecto. Esto a menudo resultará en errores que son difíciles de entender.

Esta pregunta podría interpretarse de muchas maneras, pero como mencionó a Elm en los comentarios, el contexto es más claro.

Elm es, entre otras cosas, un lenguaje de programación tipificado estáticamente . Uno de los beneficios de este tipo de sistemas tipográficos es que el compilador captura muchas clases de errores (aunque no todos) antes de que se utilice realmente el programa. Algunos tipos de errores pueden codificarse en tipos (como Result y Task de Elm), en lugar de lanzarse como excepciones. Esto es lo que quieren decir los diseñadores de Elm: muchos errores se detectarán en tiempo de compilación en lugar de en "tiempo de ejecución", y el compilador lo obligará a lidiar con ellos en lugar de ignorarlos y esperar lo mejor. Está claro por qué esto es una ventaja: es mejor que el programador se dé cuenta de un problema antes que el usuario.

Tenga en cuenta que cuando no utiliza excepciones, los errores se codifican de otras formas menos sorprendentes. De la documentación de Elm :

  

Una de las garantías de Elm es que no verá errores de tiempo de ejecución en la práctica. NoRedInk ha estado usando Elm en producción durante aproximadamente un año, ¡y todavía no han tenido uno! Como todas las garantías en Elm, esto se reduce a las opciones fundamentales de diseño de idiomas. En este caso, nos ayuda el hecho de que Elm trata los errores como datos. (¿Te has dado cuenta de que hacemos mucho los datos aquí?)

Los diseñadores de Elm son un poco audaces al afirmar "sin excepciones de tiempo de ejecución" , aunque lo califican con "en la práctica". Lo que probablemente significan es "menos errores inesperados que si estuvieras codificando en javascript".

    
respondido por el Andres F. 01.12.2016 - 18:01
0

Elm reclamaciones:

  

No hay errores de tiempo de ejecución en la práctica. No nulo No indefinido no es una función.

Pero usted pregunta acerca de las excepciones en tiempo de ejecución . Hay una diferencia.

En Elm, nada devuelve un resultado inesperado. NO puede escribir un programa válido en Elm que produzca errores de tiempo de ejecución. Por lo tanto, no necesita excepciones.

Entonces, la pregunta debería ser:

  

¿Cuál es el beneficio de no tener "errores de tiempo de ejecución"?

Si puede escribir código que nunca tenga errores de tiempo de ejecución, sus programas nunca se bloquearán.

    
respondido por el Héctor 04.03.2017 - 15:54

Lea otras preguntas en las etiquetas