2012-09-09 63 views
15

F#計算表達式有語法:爲什麼F#計算表達式需要構建器對象(而不是類)?

ident { cexpr } 

哪裏ident是生成器對象(這句法從Don Syme's 2007 blog entry拍攝)。

在我見過的所有例子中,建設者對象是單實例,和無狀態啓動。唐給予稱爲attempt定義生成器對象的例子:

let attempt = new AttemptBuilder() 

我的問題:爲什麼不F#只需直接使用AttemptBuilder類計算表達式?當然,該符號可以像實例方法調用一樣容易地去除靜態方法調用。

使用實例值意味着人們可以在理論上實例化可變的內部狀態相同類的多個製造商的對象,想必參數化在某種程度上,甚至(但願)。但我無法想象這將如何有用。


更新:我上面引述表明建造者必須顯示爲一個單一的標識符,這是一種誤導,可能反映了語言的早期版本的語法。最近F# 2.0 Language Specification定義語法爲:

expr { comp-or-range-expr } 

這清楚地表明,任何式(計算結果爲生成器對象)可被用作構建體的第一個元素。

+0

實施例:HTTPS: //github.com/mausch/fsharpx/blob/ec5f2de4c1c81aea8ef5e139b10c0cd0091ac2e5/src/FSharpx.Core/Monad.fs#L201 –

回答

13

你的假設是正確的;可以對構建器實例進行參數化,並可以在整個計算過程中隨後使用參數。

我使用這種模式來建立一個數學證明的樹到一定的計算。每個結論是三一個問題名稱,一計算結果,和底層結論(引理)的N-樹。

讓我舉一個小例子,刪除一個證明樹,但保留問題名稱。我們稱之爲註釋,因爲它似乎更合適。

type AnnotationBuilder(name: string) = 
    // Just ignore an original annotation upon binding 
    member this.Bind<'T> (x, f) = x |> snd |> f 
    member this.Return(a) = name, a 

let annotated name = new AnnotationBuilder(name) 

// Use 
let ultimateAnswer = annotated "Ultimate Question of Life, the Universe, and Everything" { 
    return 42 
} 
let result = annotated "My Favorite number" { 
    // a long computation goes here 
    // and you don't need to carry the annotation throughout the entire computation 
    let! x = ultimateAnswer 
    return x*10 
} 
+0

不錯的例子。在Haskell(我認爲也是OCaml)中,通過將monad的類型定義爲一個將問題名稱作爲參數的函數,可以實現同樣的效果。我還沒有嘗試過,但它也可能在F#中。所以這回答了我的原始問題,但是讓我想知道爲什麼F#的設計者選擇不再強調函數鏈的更純粹的數學方法。 –

6

這只是一個靈活性問題。是的,如果Builder類需要是靜態的,它會更簡單,但它確實需要開發者的一些靈活性,而沒有在過程中獲得太多的收穫。

例如,假設你想創建一個工作流程與服務器通信。在代碼中的某處,您需要指定該服務器的地址(Uri,IPAddress等)。在哪些情況下,您需要/想要在單個工作流程中與多個服務器進行通信?如果答案是'none',那麼使用構造函數創建構建器對象會更有意義,該構造器允許您傳遞服務器的Uri/IPAddress,而不必通過各種函數連續傳遞該值。在內部,您的構建器對象可能會將值(服務器地址)應用於工作流中的每個方法,從而創建類似於(但不完全)Reader類monad的內容。

隨着基於實例的建設者的對象,你也可以使用繼承來創建建設者的類型層次結構與某些遺傳功能。我還沒有看到任何人在實踐中這樣做,但又一次 - 如果人們需要它,那麼靈活性就是存在的,而靜態類型的構建器對象則不具備這種靈活性。

+0

感謝您提及Reader monad。這是一個很好的例子。 –

3

另外一個替代方法是利用單個案件識別聯合如:

type WorkFlow = WorkFlow with 
    member __.Bind (m,f) = Option.bind f m 
    member __.Return x = Some x 

然後就可以直接使用它像參數計算表達式

let x = WorkFlow{ ... } 
相關問題