Gestione degli errori

La valutazione di un'espressione M produce uno dei risultati seguenti:

  • Viene generato un singolo valore.

  • Viene restituito un errore per segnalare che il processo di valutazione dell'espressione non è riuscito a generare un valore. Un errore contiene un singolo valore di record che può essere usato per fornire informazioni aggiuntive su ciò che ha causato la valutazione incompleta.

Gli errori possono essere restituiti e anche gestiti dall'interno di un'espressione.

Generazione di errori

La sintassi per la generazione di un errore è la seguente:

error-raising-expression:
      error expression

Come sintassi abbreviata per i valori di errore possono essere usati valori di testo, Ad esempio:

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

I valori di errore completi sono record e possono essere creati tramite la funzione Error.Record:

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

L'espressione precedente è equivalente a:

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

Se viene generato un errore, la valutazione dell'espressione corrente viene arrestata e lo stack di valutazione dell'espressione viene rimosso fino a quando non si verifica una delle condizioni seguenti:

  • Viene raggiunto un campo di record, un membro di sezione o una variabile let (collettivamente definiti voce). La voce è contrassegnata con un errore, il valore di errore viene salvato con tale voce e quindi propagato. Tutti gli accessi successivi a tale voce provocheranno la generazione di un errore identico. Le altre voci del record, della sezione o dell'espressione let non sono necessariamente interessati (a meno che non accedano a una voce contrassegnata in precedenza come errore).

  • Viene raggiunta l'espressione di primo livello. In questo caso, il risultato della valutazione dell'espressione di primo livello è un errore anziché un valore.

  • Viene raggiunta un'espressione try. In questo caso, l'errore viene acquisito e restituito come valore.

Gestione degli errori

Per la gestione di un errore viene usata un'espressione error-handling-expression (nota in modo informale come "espressione try"):

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

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

Quando si valuta un'espressione error-handling-expression senza otherwise-clause, sono valide le considerazioni seguenti:

  • Se la valutazione di protected-expression non genera un errore e produce un valore x, il valore prodotto da error-handling-expression è un record nel formato seguente:
    [ HasErrors = false, Value = x ]
  • Se la valutazione di protected-expression genera un valore di errore "e", il risultato di error-handling-expression è un record nel formato seguente:
    [ HasErrors = true, Error = e ]

Quando si valuta un'espressione error-handling-expression con error-handler, sono valide le considerazioni seguenti:

  • L'espressione protected-expression deve essere valutata prima di error-handler.

  • Il gestore di errori error-handler deve essere valutato solo se la valutazione di protected-expression genera un errore.

  • Se la valutazione di protected-expression genera un errore, il valore prodotto da error-handling-expression è il risultato della valutazione di error-handler.

  • Gli errori generati durante la valutazione del gestore di errori error-handler vengono propagati.

  • Quando il gestore degli errori error-handler valutato è unacatch-clause, viene richiamata la funzione catch-function. Se tale funzione accetta un parametro, il valore di errore verrà passato come valore.

Nell'esempio seguente viene illustrata un'espressione error-handling-expression nel caso in cui non venga generato alcun errore:

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

Nell'esempio seguente vengono illustrate la generazione di un errore e la relativa gestione:

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

L'esempio precedente può essere riscritto con una sintassi minore usando una catch-clause con una catch-function che accetta un parametro:

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

È possibile usare una clausola otherwise-clause per sostituire gli errori gestiti da un'espressione try con un valore alternativo:

try error "A" otherwise 1 
// 1

Una clausola catch-clause con una funzione catch-function a parametro zero è in effetti una sintassi alternativa più lunga per una clausola otherwise-clause:

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

Se anche il gestore degli errori error-handler genera un errore, viene eseguita l'intera espressione try:

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"

Errori negli inizializzatori di record e let

Nell'esempio seguente viene illustrato un inizializzatore di record con un campo A che genera un errore e a cui accedono altri due campi, B e C. Il campo B non gestisce l'errore generato da A, mentre C lo gestisce. Il campo finale D non accede A e pertanto non è interessato dall'errore in 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 
]

Il risultato della valutazione dell'espressione precedente è il seguente:

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

La gestione degli errori in M deve essere eseguita in prossimità della causa degli errori in modo da gestire gli effetti dell'inizializzazione di campi differita e delle valutazioni di chiusura posticipate. Nell'esempio seguente viene illustrato un tentativo non riuscito di gestione di un errore tramite un'espressione try:

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

In questo esempio, la definizione g è stata pensata per gestire l'errore generato durante la chiamata a f. Tuttavia, l'errore viene generato da un inizializzatore di campo che viene eseguito solo quando necessario e quindi dopo che il record è stato restituito da f e passato tramite l'espressione try.

Errore di non implementazione

Quando sviluppa un'espressione, un autore potrebbe voler escludere dall'implementazione alcune parti, ma essere comunque in grado di eseguire l'espressione. Un modo per gestire un caso di questo tipo consiste nel generare un errore per le parti non implementate, Ad esempio:

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

Il simbolo dei puntini di sospensione (...) può essere usato come scorciatoia per error.

not-implemented-expression:
      ...

Il codice che segue, ad esempio, è equivalente all'esempio precedente:

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