2017-02-24 3 views
2

Ich versuche, Computerausdrücke zu verwenden, um eine Builder-ähnliche DSL zu erstellen, aber wenn ich versuche, mit Zuweisungen zu helfen, Dinge zu komponieren, bekomme ich einen Kompilierungsfehler, der solche Zuordnungen kann nicht gefunden werden. Hier ein Beispiel:Kann Variablen innerhalb eines Berechnungsausdrucks nicht wiederverwenden

type Node = 
    { 
     Key: Option<string> 
     Children: List<Node> 
     XPathFromParent: string 
    } 


let defaultNode = 
    { 
     Key = None; 
     Children = []; 
     XPathFromParent = ".//somePath" 
    } 


type NodeBuilder(xpath: string) = 
    member self.Yield(item: 'a): Node = defaultNode 

    member this.xpath = xpath 

    [<CustomOperation("xpath_from_parent")>] 
    member __.XPathFromParent (node, x) = {node with XPathFromParent = x} 

    [<CustomOperation("nodes")>] 
    member __.Nodes (node, x) = {node with Children = x} 

    [<CustomOperation("key")>] 
    member __.MidasMeasurementKey (node, x) = {node with Key = x} 

    member this.Bind(x, f) = f x 


let node xpath = NodeBuilder(xpath) 


let rootNode = node ".//somePath" { 
    let! childNodes = 
     [ 
      node "somepath" { 
       nodes [] 
      }; 

      node "someOtherPath" { 
       nodes [] 
      } 
     ] 

    nodes childNodes // The value or constructor 'childNodes' is not defined. 
} 

Wie kann ich diesen Code ändern, so dass ich die childNodes Zuordnung verweisen kann es in den nodes benutzerdefinierten Operator übergeben?

Antwort

4

Berechnung Ausdrücke können schwierig sein, bis Sie vollständig verstehen, wie sie arbeiten. Wenn Sie mit F # noch relativ neu sind, würde ich vorschlagen, ohne die Berechnungsausdrücke zu gehen und einfache Funktionsaufrufe und Listen zu verwenden, um Ihre Knoten zu konstruieren. Etwas wie folgt aus:

type Node = 
    { 
     Key: Option<string> 
     Children: List<Node> 
     XPathFromParent: string 
    } 

let defaultNode = 
    { 
     Key = None; 
     Children = []; 
     XPathFromParent = ".//somePath" 
    } 

let withNodes children node = { node with Children = children } 
let withXpathFromParent xpath node = { node with XPathFromParent = xpath } 
let withKey key node = { node with Key = Some key } 

let mkNode xpath children = { Key = None 
           Children = children 
           XPathFromParent = xpath } 

// Usage example 

let rootNode = 
    mkNode ".//somePath" [ 
     mkNode "somepath" [] |> withKey "childkey1" 
     mkNode "someOtherPath" [] // No key specified, so this one will be None 
    ] |> withKey "parentKey" 

, die erzeugt ein rootNode die wie folgt aussieht:

val rootNode : Node = 
    {Key = Some "parentKey"; 
    Children = 
    [{Key = Some "childkey1"; 
     Children = []; 
     XPathFromParent = "somepath";}; {Key = null; 
             Children = []; 
             XPathFromParent = "someOtherPath";}]; 
    XPathFromParent = ".//somePath";} 
+1

Hey @rmunn, du hast richtig geraten, dass ich neu bei F # bin :). Ich bin jedoch wirklich auf der Suche nach dem Einsatz von Berechnungsausdrücken. Ich versuche, eine Builder-DSL zu erstellen, die auch technisch nicht versierte Personen für ein unternehmensspezifisches Problem meines Unternehmens schreiben können - meiner Meinung nach ist die hypothetische Version mit Berechnungsausdrücken viel einfacher für die Augen. Wenn es nur für technische Leute nur wäre, würde ich tun, wie Sie klug vorgeschlagen haben. – nebffa

+1

Dann kann ich dir nicht viel helfen: Ich kann dein Problem reproduzieren, aber es stopft mich auch. Der beste Vorschlag, den ich machen kann, ist 'printfn' Anweisungen zu sprengen, aka der Debugger des armen Mannes :-), in deinen Erbauer und sehen wann (und wenn) sie angerufen werden. – rmunn

+1

Aber ein anderer Vorschlag, den ich habe, ist, wenn Sie eine DSL für Nicht-Techies bauen möchten, sind Berechnungsausdrücke möglicherweise nicht so gut wie das Rollen Ihrer eigenen kleinen Sprache mit [FParsec] (http://www.quantec.com/fparsec) /tutorial.html). Die Fehlermeldungen von Berechnungsausdrücken sind selbst für erfahrene Entwickler eher undurchsichtig; Nicht-Techniker werden es * unmöglich * finden, herauszufinden, dass sie (sagen wir) ein Wort ausgelassen haben (z.B., schrieb '[]', wenn sie 'nodes []' hätten schreiben sollen. Wenn Sie FParsec verwenden, um ihnen eine benutzerdefinierte Sprache zum Schreiben zu geben, können Sie die Syntax und die Fehlermeldungen steuern. – rmunn

5

Ihr unmittelbares Problem ist, dass Sie auf alle Argumente, um benutzerdefinierte Operatoren müssen setzen, um eine [<ProjectionParameter>] Attribut, das Sie möchten in der Lage sein, auf den Variablenraum des Berechnungsausdrucks zuzugreifen. Sobald Sie dies jedoch hinzufügen, werden Sie feststellen, dass Sie Probleme mit nicht übereinstimmenden Typen haben. Im Allgemeinen stimme ich mit rmunn überein: Berechnungsausdrücke sind nicht unbedingt eine gute Lösung für Ihr Problem, Sie sollten also in Betracht ziehen, stattdessen einen anderen Mechanismus zu verwenden.

Wenn Sie jedoch darauf bestehen, weiter zu gehen, gibt es einen Trick, der Ihnen beim Debuggen hilft. Es sieht aus wie Sie in der Lage sein wollen, schreiben

node "something" { 
    let! childNodes = ([some expression]:Node list) 
    nodes childNodes 
} 

So ein Dummy-Builder wie diese schaffen (die scheinbar nutzlose Quote Methode ist der Schlüssel):

type DummyNodeBuilder(xpath:string) = 
    [<CustomOperation("nodes")>] 
    member __.Nodes (node:Node, [<ProjectionParameter>]x) = node // Note: ignore x for now and pass node through unchanged 
    member __.Yield(_) = Unchecked.defaultof<_> // Note: don't constrain types at all 
    member __.Bind(_,_) = Unchecked.defaultof<_> // Note: don't constrain types at all 
    member __.Quote() =() 

let node xpath = DummyNodeBuilder xpath 

let expr = 
    node "something" { 
     let! childNodes = [] : Node list 
     nodes childNodes 
    } 

und Sie werden sehen, dass expr hält ein Zitat entspricht in etwa:

builder.Nodes(
    builder.Bind([], 
       fun childNodes -> builder.Yield childNodes), 
    fun childNodes -> childNodes) 

so in Ihrem realen Builder müssen Sie Methoden, die kompatible Signaturen (zB Nodes ‚s Sekunde haben eine rgument muss eine Funktion akzeptieren, und das erste Argument muss mit dem Rückgabetyp von Bind usw. kompatibel sein. Wenn Sie andere Workflows ausprobieren, die Sie mit dem Dummy-Builder aktivieren möchten, können Sie sehen, wie sie zusätzliche Abhängigkeiten ermitteln und erkennen.

Verwandte Themen