2013-08-27 17 views
11

Ultimamente ho insegnato a me F # e provengo da uno sfondo imperativo (C++/C#). Come esercizio ho lavorato su funzioni che possono fare roba con matrici, come aggiungere, moltiplicare, ottenere determinanti, ecc. Tutto sta andando bene a questo proposito, ma trovo che forse non sto prendendo le decisioni migliori quando si tratta di gestire input non validi, ad esempio:F #: alcuni, nessuno o eccezione?

// I want to multiply two matrices 
let mult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    // Here is where I am running to conceptual trouble: 
    // In a C# world, I would throw an exception. 
    if !sizeOK then 
    raise (InvalidOperationException("bad dimensions!") 
    else 
    doWork m1 m2 

Quindi, mentre questo funziona tecnicamente, è appropriato per un linguaggio funzionale? È nello spirito della programmazione funzionale? O sarebbe più sensato per riscrivere come:

let mult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    if !sizeOK then 
    None 
    else 
    Some doWork m1 m2 

In questo caso sto tornando un'opzione, che aggiunge un ulteriore livello intorno alla matrice, ma potrebbe anche utilizzare i risultati della funzione, anche in un fallimento casi (Nessuno) con abbinamento di pattern, ecc. in qualche momento successivo nel programma. Quindi esiste una best practice per questi tipi di scenari? Cosa farebbe un programmatore funzionale?

+0

Credo che quest'ultimo sia più nello "spirito" di FP. Potresti voler esaminare la Forse monade e questo è un approccio comune per gestire gli input negativi. –

+0

Come detto nella risposta, ho anche implementato spesso due funzioni, una che genera errori e una identica funzione di prova che restituisce un'opzione/scelta invece di lanciare. poi uso quello che rende il mio codice finale più carino :) per una più facile gestione di Option/Choice che potresti voler leggere "programmazione orientata alla ferrovia": http://fsharpforfunandprofit.com/posts/recipe-part2/ – stmax

risposta

8

ho tendono ad evitare di eccezioni per i seguenti motivi:

  • .NET exceptions are slow
  • eccezioni cambiano i flussi di controllo dei programmi in modo inaspettato, che lo rende molto più difficile ragionare su
  • eccezioni spesso sorgere in situazioni critiche mentre è possibile eseguire il fail-safe utilizzando le opzioni.

Nel tuo caso, io ti seguirò F # fondamentali convenzioni di libreria (ad esempio List.tryFind e List.find, ecc) e creare entrambe le versioni:

let tryMult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    if not sizeOK then 
    None 
    else 
    Some <| doWork m1 m2 

let mult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    if not sizeOK then 
    raise <| InvalidOperationException("bad dimensions!") 
    else 
    doWork m1 m2 

Questo esempio non è eccezionale sufficiente per utilizzare le eccezioni . La funzione mult è inclusa per la compatibilità con C#. Qualcuno che usa la tua libreria in C# non ha pattern matching per scomporre facilmente le opzioni.

Uno svantaggio con le opzioni è che non forniscono il motivo per cui la funzione non ha prodotto un valore. È eccessivo qui; generalmente Choice (o entrambi monade in termini Haskell) è più adatto per error handling:

let tryMult m1 m2 = 
    // Assume that you need to validate input 
    if not (validateInput m1) || not (validateInput m2) then 
    Choice2Of2 <| ArgumentException("bad argument!") 
    elif not <| validateDims m1 m2 then 
    Choice2Of2 <| InvalidOperationException("bad dimensions!") 
    else 
    Choice1Of2 <| doWork m1 m2 

E 'un peccato che F # Nucleo manca funzioni di ordine superiore per manipolare Choice. È possibile trovare tali funzioni nella libreria FSharpX o ExtCore.

3

tendo ad andare con le seguenti linee guida:

Usa eccezione in una funzione che dovrebbe sempre avere un valori di ritorno, quando qualcosa va storto in modo imprevisto. Questo potrebbe ad es. sii se gli argomenti non obbediscono al contratto per la funzione. Questo ha il vantaggio che il codice client diventa più semplice.

Utilizzare un'opzione quando la funzione talvolta ha un valore di ritorno per l'input valido. Questo potrebbe ad es. essere visualizzato su una mappa in cui una chiave valida potrebbe non esistere. In tal modo si forza l'utente a verificare se la funzione ha un valore di ritorno. Questo potrebbe ridurre i bug, ma ingombra sempre il codice client.

Il tuo caso è un po 'in mezzo. Se si prevede che venga utilizzato principalmente in luoghi in cui le dimensioni sono valide, farei un'eccezione. Se si prevede che il codice client lo chiami spesso con una dimensione non valida, restituirei un'opzione. Io probabilmente andare con il primo, come è più pulito (vedi sotto), ma io non conosco il tuo contesto:

// With exception 
let mult3 a b c = 
    mult (mult a b) c; 

// With option 
let mult3 a b c= 
    let option = mult a b 
    match option with 
    | Some(x) -> mult x b 
    | None -> None 

Diniego: Non ho esperienza professionale con la programmazione funzionale, ma io sono un TA in F # programmazione a livello di laurea.

2

Mi piacciono le risposte di cui sopra ma volevo aggiungere un'altra opzione. Dipende davvero da quanto è inatteso il risultato e se ha senso procedere. Se si tratta di un evento raro e il chiamante probabilmente non ha intenzione di fallire, allora un'eccezione è del tutto rispettabile. Il codice per catturare l'eccezione può essere di molti livelli sopra e il chiamante probabilmente non ha intenzione di fallire. Se è davvero un risultato di routine per un'operazione fallire, Some/None è ok anche se ti dà solo due opzioni e nessun modo di passare un risultato. Un'altra opzione è quella di creare un'unione discriminata di possibilità. Questo costringe il chiamante a corrispondere ai diversi risultati, è estendibile e non obbliga a rendere ogni risultato lo stesso tipo di dati.

ad es.

type MultOutcome = 
    | RESULT of Matrix 
    | DIMERROR 
    | FOOERROR of string 


let mult a b = 
    if dimensionsWrong then 
     DIMERROR 
    elif somethingElseIDoNotLike then 
     FOOERROR("specific message") 
    else 
     DIMRESULT(a*b) 


match mult x y with 
    | DIMERROR -> printfn "I guess I screwed up my matricies" 
    | FOOERROR(s) -> printfn "Operation failed with message %s" s 
    | DIMRESULT(r) -> 
     // Proceed with result r