2012-08-15 15 views
7

Ho un metodo C# che sembra un po 'come questo:Come posso rifattorizzare la clausola else richiesta?

bool Eval() { 
    // do some work 
    if (conditionA) { 
    // do some work 
    if (conditionB) { 
     // do some work 
     if (conditionC) { 
     // do some work 
     return true; 
     } 
    } 
    } 
    return false; 
} 

In F # questo finisce per guardare un bel po' più brutta a causa dei rami obbligatorie altro:

let eval() = 
    // do some work 
    if conditionA then 
    // do some work 
    if conditionB then 
     // do some work 
     if conditionC then 
     // do some work 
     true 
     else 
     false 
    else 
     false 
    else 
    false 

Quale potrebbe essere un pulitore modo di scrivere questo in F #?

+2

C'è sicuramente un modo più pulito di scrivere questo in C#. Inversa le tue condizioni e restituisci false. Non sono sicuro se aiuta a F # però. –

+0

Wow: molte risposte positive su questa domanda. –

+0

@Onorio - Non sono d'accordo; ci sono molte risposte "interessanti", ma secondo me, la maggior parte delle risposte aggiungono complessità e ho pensato che il nostro obiettivo come ingegneri del software fosse quello di ridurlo. – Brian

risposta

6

Ebbene, dal momento che 'fare un certo lavoro' è già imperativo (presumibilmente), allora penso che

let eval() = 
    let mutable result = false 
    ... // ifs 
     result <- true 
    ... // no more elses 
    result 

è più breve e ragionevole. (In altre parole, else è obbligatorio solo per i if espressioni che restituiscono valori, in quanto si sta facendo un lavoro indispensabile, utilizzare if dichiarazioni che non hanno bisogno di un else.)

+0

Scelti per la sua semplicità e applicabilità anche ad altre lingue. – Asik

4

Utilizzando le funzioni di ordine superiore nel modulo Option può rendere questo flusso molto pulito senza alcun stato mutevole:

let Eval() = 
    // do some work 
    if not conditionA then None else 
     // do some work 
     Some state 
    |> Option.bind (fun state -> 
     if not conditionB then None else 
      // do some work 
      Some state') 
    |> Option.bind (fun state -> 
     if not conditionC then None else 
      // do some work 
      Some true) 
    |> defaultArg <| false 

O per maggiore chiarezza, utilizzando denominate funzioni, piuttosto che lambda:

let Eval() = 
    let a() = 
     if not conditionA then None else 
      // do some work 
      Some state 
    let b state = 
     if not conditionB then None else 
      // do some work 
      Some state' 
    let c state = 
     if not conditionC then None else 
      // do some work 
      Some true 
    // do some work 
    a() |> Option.bind b |> Option.bind c |> defaultArg <| false 
+0

Piuttosto Haskellish (il che significa che mi piace) - questo sfrutta il fatto che Option è una monade. È un peccato che F # non venga con un'espressione di calcolo per l'opzione monad in realtà.Ci sono alcune implementazioni sul web, ma sarebbe bello se fosse nel nucleo. –

6

Si prega di non aver paura di estrarre le funzioni. Questa è la chiave per controllare la logica complessa.

let rec partA() = 
    // do some work 
    let aValue = makeA() 
    if conditionA 
    then partB aValue 
    else false 
and partB aValue = 
    // do some work 
    let bValue = makeB aValue 
    if conditionB 
    then partC bValue 
    else false 
and partC bValue = 
    // do some work 
    conditionC 
+0

Ciò presuppone che le parti non condividano lo stato. – Daniel

+2

@Daniel: lo stato può essere passato come parametri. – ChaosPandion

13
module Condition = 
    type ConditionBuilder() = 
    member x.Bind(v, f) = if v then f() else false 
    member x.Return(v) = v 
    let condition = ConditionBuilder() 

open Condition 

let eval() = 
    condition { 
    // do some work 
    do! conditionA 
    // do some work 
    do! conditionB 
    // do some work 
    do! conditionC 
    return true 
    } 
+3

@Brian Credo che il tuo commento sia un po 'fuori luogo. Le persone possono decidere da sole ciò che è e ciò che non è leggibile, e ciò che è e ciò che non è una buona risposta. Etica a parte, la leggibilità è negli occhi di chi guarda: persone diverse provenienti da contesti diversi hanno più o meno tempo a leggere stili di codifica diversi. –

+2

La mia opinione è che questa è una risposta terribile. Penso che la stragrande maggioranza dei programmatori preferirebbe vedere il codice originale, o il codice come il mio con ifstens di base e una singola variabile mutabile locale, rispetto a questo codice che usa le caratteristiche del linguaggio esoterico per introdurre una nuova astrazione di controllo al fine di .. Non so cosa, essere più elegante? Salva una riga? Io proprio non capisco. Immagino ci siano più sottoculture nella comunità di FP, e sono completamente fuori dal mondo con questo. – Brian

+5

@Brian: FWIW, non faccio parte della "sub-cultura" FP faticosa. Ma potresti obiettare che questo è un annuncio per F # come dovrebbe essere: 1) interessante/divertente, 2) dimostrare il toolkit piuttosto vario di F # per risolvere i problemi, e 3) stimolare l'interesse per concetti che valgono la pena di imparare e potrebbero essere nuovi per alcuni programmatori (monadi). È questo _awful_? – Daniel

9

Come accennato nei commenti, si potrebbe invertire la condizione. Questo semplifica il codice C#, perché si può scrivere:

if (!conditionA) return false; 
// do some work 

Sebbene F # non ha resi imperativi (se si desidera tornare, è necessario entrambi i rami vere e false), in realtà semplifica questo codice un po 'troppo, perché si può scrivere:

let eval() = 
    // do some work 
    if not conditionA then false else 
    // do some work 
    if not conditionB then false else 
    // do some work 
    if not conditionC then false else 
    // do some work 
    true 

Hai ancora scrivere false più volte, ma almeno non si dispone di rientrare il codice di troppo. Esiste un numero illimitato di soluzioni complesse, ma questa è probabilmente l'opzione più semplice. Per quanto riguarda la soluzione più complessa, è possibile utilizzare uno F# computation expression that allows using imperative-style returns. Questo è simile al calcolo di Daniel, ma un po 'più potente.

1

Si potrebbe rendere il codice in una sorta di tabella di verità, che a seconda del caso nel mondo reale potrebbe rendere più esplicito:

let condA() = true 
let condB() = false 
let condC() = true 

let doThingA() = Console.WriteLine("Did work A") 
let doThingB() = Console.WriteLine("Did work B") 
let doThingC() = Console.WriteLine("Did work C") 

let Eval() : bool = 
    match condA(), condB(), condC() with 
    | true, false, _  -> doThingA();       false; 
    | true, true, false -> doThingA(); doThingB();    false; 
    | true, true, true -> doThingA(); doThingB(); doThingC(); true; 
    | false, _,  _  ->          false; 
+0

... anche se meno efficienti come condB() e condC() potrebbero essere chiamati inutilmente. – Kit

+0

Per vedere la potenza di questo approccio BTW, provare a commentare, dire la seconda condizione di corrispondenza. In VS, ottieni un blu esagerato e un avvertimento che non hai coperto tutti i casi. – Kit

+1

Mi piace questo, il pattern matching può essere estremamente chiaro. Questo ha anche una sensazione distinta :) – 7sharp9

Problemi correlati