2017-02-24 40 views
2

我試圖使用計算表達式來創建類似於構建器的DSL,但是當我嘗試使用let賦值來幫助編寫內容時,出現編譯錯誤,此類賦值無法找到。這裏有一個例子:不能重新使用在計算表達式中分配的變量

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. 
} 

我怎樣才能改變這種代碼,這樣我可以參考childNodes分配給它傳遞到nodes運營商定製?

回答

4

計算表達式可能很難使用,直到您完全知道如何工作。如果你對F#比較陌生,我會建議在沒有計算表達式的情況下使用普通函數調用和列表來構造節點。像下面這樣:

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" 

產生一個rootNode,看起來像這樣:

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

嘿@rmunn,你已經正確地猜到了我是F#的新手:)。但是,我真的特別在尋找如何用計算表達式來實現這一點。我想要做的是創建一個構建器DSL,即使非技術人員也可以爲我的公司所面臨的業務特定問題撰寫 - 在我看來,具有計算表達式的假設版本在眼中更容易。如果僅僅是技術人員,我只會按照你明智的建議去做。 – nebffa

+1

然後我無法爲你提供很多幫助:我可以重現你的問題,但它也在困擾着我。我可以做的最好的建議是將'printfn'語句,也就是窮人的調試器:-)撒到你的構建器中,看看它們何時(以及如果)被調用。 – rmunn

+1

但我的另一個建議是,如果你想爲非技術人員構建DSL,計算表達式可能不如用[FParsec](http://www.quanttec.com/fparsec)滾動你自己的小語言/tutorial.html)。即使對於有經驗的開發人員來說,來自計算表達式的錯誤消息也往往有點不透明。非技術人員會發現他們不可能*弄清楚他們(說)丟掉了一個單詞(例如,,當他們應該寫入'nodes []''時寫了'']'。而如果您使用FParsec爲其提供自定義語言來編寫,則可以控制語法和錯誤消息。 – rmunn

5

你眼前的問題是,你需要把[<ProjectionParameter>]屬性上,你希望的任何參數,以運營商定製能夠訪問計算表達式的變量空間。然而,一旦你添加了這個,你會發現你有一些不匹配類型的問題。一般來說,我同意rmunn:計算表達式不一定適合您的問題,所以您應該強烈考慮使用不同的機制。

但是,如果你堅持向前推,這裏有一個技巧可以幫助你調試。它看起來像你希望能夠寫出

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

因此,創建一個虛擬的建設者這樣的(貌似無用Quote方法是關鍵):

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 
    } 

,你會看到expr持有報價大致相當於:

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

所以你的真實製造商就需要有一個具有兼容簽名的方法(如Nodes的第二個一參數必須接受函數,並且第一個參數必須與返回類型Bind等兼容)。在嘗試使用虛擬生成器啓用的其他工作流程時,可以看到他們如何解除並發現其他約束。