Gestion des erreurs

Le résultat de l’évaluation d’une expression M produit l’un des résultats suivants :

  • Une valeur unique est produite.

  • Une erreur est générée, indiquant que le processus d’évaluation de l’expression n’a pas pu produire de valeur. Une erreur contient une valeur d’enregistrement unique qui peut être utilisée pour fournir des informations supplémentaires sur ce qui a provoqué l’évaluation incomplète.

Les erreurs peuvent être générées à partir d’une expression, et peuvent être gérées à partir d’une expression.

Génération d’erreurs

La syntaxe permettant de générer une erreur est la suivante :

error-raising-expression:
      errorexpression

Des valeurs de texte peuvent être utilisées comme raccourcis pour les valeurs d’erreur. Par exemple :

error "Hello, world" // error with message "Hello, world"

Les valeurs d’erreur complètes sont des enregistrements, et peuvent être construites à l’aide de la fonction Error.Record :

error Error.Record("FileNotFound", "File my.txt not found",
     "my.txt")

L’expression ci-dessus équivaut à :

error [ 
    Reason = "FileNotFound", 
    Message = "File my.txt not found", 
    Detail = "my.txt" 
]

La génération d’une erreur provoque l’arrêt de l’évaluation de l’expression en cours, et la pile d’évaluation de l’expression se déroule jusqu’à ce que l’un des éléments suivants se produise :

  • Un champ d’enregistrement, un membre de section ou une variable let (collectivement : une entrée) est atteint. L’entrée est marquée comme ayant une erreur, la valeur d’erreur est enregistrée avec cette entrée, puis propagée. Tout accès ultérieur à cette entrée entraîne la génération d’une erreur identique. Les autres entrées de l’enregistrement, de la section ou de l’expression let ne sont pas nécessairement affectées (sauf si elles accèdent à une entrée précédemment marquée comme ayant une erreur).

  • L’expression de niveau supérieur est atteinte. Dans ce cas, le résultat de l’évaluation de l’expression de niveau supérieur est une erreur plutôt qu’une valeur.

  • Une expression try est atteinte. Dans ce cas, l’erreur est capturée et retournée en tant que valeur.

Gestion des erreurs

Une error-handling-expression (expression de gestion des erreurs, appelée informellement « expression try ») est utilisée pour gérer une erreur :

error-handling-expression:
      tryprotected-expression error-handleropt
protected-expression:
      expression
error-handler :
      otherwise-clause
      catch-clause
otherwise-clause :

      otherwisedefault-expression
default-expression :
      expression
catch-clause :
      catchcatch-function
catch-function :
      (parameter-nameopt)=>function-body

Le comportement suivant s’applique lors de l’évaluation de error-handling-expression sans error-handler :

  • Si l’évaluation de la protected-expression ne provoque pas d’erreur et produit une valeur x, la valeur produite par error-handling-expression est un enregistrement au format suivant :
    [ HasErrors = false, Value = x ]
  • Si l’évaluation de protected-expression génère une valeur d’erreur e, le résultat de error-handling-expression est un enregistrement au format suivant :
    [ HasErrors = true, Error = e ]

Le comportement suivant s’applique lors de l’évaluation de error-handling-expression avec error-handler :

  • protected-expression doit être évalué avant error-handler.

  • error-handler doit être évalué si et seulement si l’évaluation de protected-expression génère une erreur.

  • Si l’évaluation de protected-expression génère une erreur, la valeur produite par error-handling-expression est le résultat de l’évaluation de error-handler.

  • Les erreurs générées lors de l’évaluation de error-handler sont propagées.

  • Quand error-handler qui est évalué est catch-clause, catch-function est appelée. Si cette fonction accepte un paramètre, la valeur d’erreur est transmise en tant que valeur.

L’exemple suivant montre une error-handling-expression dans le cas où aucune erreur n’est générée :

let
    x = try "A"
in
    if x[HasError] then x[Error] else x[Value] 
// "A"

L’exemple suivant montre comment générer une erreur, puis la gérer :

let
    x = try error "A" 
in
    if x[HasError] then x[Error] else x[Value] 
// [ Reason = "Expression.Error", Message = "A", Detail = null ]

L’exemple précédent peut être réécrit avec moins de syntaxe à l’aide de catch-clause avec catch-function qui accepte un paramètre :

let
    x = try error "A" catch (e) => e
in
    x
// [ Reason = "Expression.Error", Message = "A", Detail = null ]

Vous pouvez utiliser otherwise-clause pour remplacer les erreurs gérées par une expression try par une autre valeur :

try error "A" otherwise 1 
// 1

Une catch-clause avec catch-function avec valeur de paramètre nulle est en fait une syntaxe alternative plus longue pour otherwise-clause :

try error "A" catch () => 1 
// 1

Si error-handler génère également une erreur, l’expression try entière en génère également une :

try error "A" otherwise error "B" 
// error with message "B"
try error "A" catch () => error "B" 
// error with message "B"
try error "A" catch (e) => error "B" 
// error with message "B"

Erreurs dans les initialiseurs let et d’enregistrement

L’exemple suivant montre un initialiseur d’enregistrement avec un champ A qui génère une erreur et qui est sollicité par deux autres champs B et C. Le champ B ne gère pas l’erreur générée par A, mais C la gère. Le champ final D n’accède pas à A et n’est donc pas affecté par l’erreur dans A.

[ 
    A = error "A", 
    B = A + 1,
    C = let x =
            try A in
                if not x[HasError] then x[Value]
                else x[Error], 
    D = 1 + 1 
]

Le résultat de l’évaluation de l’expression ci-dessus est le suivant :

[ 
    A = // error with message "A" 
    B = // error with message "A" 
    C = "A", 
    D = 2 
]

La gestion des erreurs en M doit être effectuée à proximité de la cause des erreurs, afin de traiter les effets de l’initialisation différée des champs et des évaluations de clôture différée. L’exemple suivant montre une tentative infructueuse de gestion d’une erreur à l’aide d’une expression try :

let
    f = (x) => [ a = error "bad", b = x ],
    g = try f(42) otherwise 123
in 
    g[a]  // error "bad"

Dans cet exemple, la définition g était destinée à gérer l’erreur générée lors de l’appel à f. Toutefois, l’erreur est générée par un initialiseur de champ qui s’exécute uniquement quand cela est nécessaire et, par conséquent, après que l’enregistrement a été retourné à partir de f et transmis par le biais de l’expression try.

Erreur non implémentée

Pendant qu’une expression est en cours de développement, un auteur peut souhaiter ignorer l’implémentation de certaines parties de l’expression tout en voulant quand même pouvoir exécuter l’expression. L’une des manières de gérer ce cas consiste à générer une erreur pour les parties non implémentées. Par exemple :

(x, y) =>
     if x > y then
         x - y
     else
         error Error.Record("Expression.Error", 
            "Not Implemented")

Le symbole des points de suspension (...) peut être utilisé comme raccourci pour error.

not-implemented-expression:
      ...

Par exemple, l’exemple suivant équivaut à l’exemple précédent :

(x, y) => if x > y then x - y else ...