Esiste un modo per creare espressioni di calcolo annidate?

4

In F #, voglio costruire una struttura gerarchica dei dati in un modo con un minimo quantità di rumore del linguaggio. Il vero problema è provare a creare un RSpec framework ispirato usando F #. RSpec consente la costruzione di test in modo annidato. Ad esempio.

describe Order do
  context "with no items" do
    it "behaves one way" do
      # ...
    end
  end

  context "with one item" do
    it "behaves another way" do
      # ...
    end
  end
end

Ho qualcosa che funziona qui , ma ci sono alcuni potenziali problemi con la soluzione attuale. In particolare generale il codice di setup / teardown può diventare alquanto brutto.

La mia preoccupazione principale è scrivere un'API che permetta all'utente di scrivere test / specifiche in un modo, in cui la lingua diventa così piccola come possibile.

Penso che l'uso delle espressioni di calcolo possa consentire al mio di creare una migliore API, ma sono in difficoltà con l'implementazione di contesti nidificati.

Attualmente ho un builder che mi permette di scrivere:

let specs =
    describe "Some feature" {
        it "has some behavior" (fun () -> 
            // Test code goes here
        )
    }

Ma per avere contesti annidati devo essere in grado di scrivere qualcosa di simile in questo modo:

let specs =
    describe "Some feature" {
        describe "in some context" {
            it "has some behavior" (fun () -> 
                // Test code goes here
            )
        }
        describe "in some other context" {
            it "has some behavior" (fun () -> 
                // Test code goes here
            )
        }
    }

Non ho idea di come implementare il contesto nidificato. O anche se è possibile piega F # in modo da permettermi di creare un tale costruttore.

Ho fatto un esperimento, che mi ha permesso di scrivere questo:

let specs =
    desribe "Some feature" {
        child (describe "in some other" {
            it "has some behavior" (fun () -> 
                // Test code goes here
            )
        })
        child (describe "in some context" {
            it "has some behavior" (fun () -> 
                // Test code goes here
            )
        })
    }

Ma la parentesi aggiunta e la costruzione esplicita del builder sono esattamente ciò che io voglio evitare in primo luogo.

type DescribeBuilder(name : string) =
    [<CustomOperation("it")>]
    member __.It (x, name, test : unit -> unit) =
        let t = TestContext.createTest name test
        x |> TestContext.addTest t
    [<CustomOperation("child")>]
    member __.Child(x, child :TestContext.T) =
        x |> TestContext.addChildContext child
    member __.Yield y =
        TestContext.create name
    member __.Delay (x) =
        x()
let describe name = new DescribeBuilder(name)
    
posta Pete 18.11.2013 - 16:20
fonte

3 risposte

2

Hai provato una delle parole chiave bang ( ! )? Potresti riuscire a farlo in questo modo:

let specs =
    describe "Some feature" {
        do! describe "in some context" {
            do! it "has some behavior" (fun () -> 
                // Test code goes here
            )
        }
        do! describe "in some other context" {
            do! it "has some behavior" (fun () -> 
                // Test code goes here
            )
        }
    }

supponendo che it restituisca qualcosa nel flusso di lavoro describe .

(Quanto sopra potrebbe essere F # improprio visto che ne sono rimasto lontano per un po ', ma dovresti essere in grado di fare qualcosa del genere.)

    
risposta data 18.11.2013 - 20:31
fonte
0

Per questo, non vedo perché vorresti usare le espressioni di calcolo, basterebbe una semplice unione discriminata con un singolo caso:

type Hierarchy = Hierarchy of Hierarchy list

let h = Hierarchy [ Hierarchy []; Hierarchy [ Hierarchy [] ] ]

È possibile che tu abbia bisogno di qualcosa di più, ma non è chiaro dal tuo esempio.

    
risposta data 18.11.2013 - 17:53
fonte
0

Ho visto il tuo progetto fspec e per proxy sono arrivato a questa domanda. Mi piace il tuo tentativo di usare le espressioni di calcolo (CE). Sembra che tu stia cercando di usarli come definizioni di macro.

L'esempio di Ruby utilizza contesto invece di un secondo descrivi . Forse puoi fare lo stesso.

let specs = 
    describe "Order" {
        context "with no items"
        it "behaves one way" (fun () -> 0)

        context "with one item"
        it "behaves another way" (fun () -> 1)
    }

Solo i membri potrebbero dare la stessa espressività senza bisogno di nidificazione. Puoi mettere insieme il rendimento e il codice figlio per il contesto. Zero può avere un TestContext vuoto creato e può funzionare con il contesto figlio inoltrato come x invece che su uno esplicito.

x.createTest name test
|> x.addTest
f x // forward through continuation

link

link

Quindi puoi fare il nesting dato che hai Delay and Return (Yield) definito, ma potrebbe essere rumoroso con le tue operazioni personalizzate. Grazie. Buona giornata.

    
risposta data 01.10.2016 - 04:36
fonte

Leggi altre domande sui tag