3

我從F#開始,在理解語法方面取得了一些進展。但是,我仍然不清楚使用F#功能的最佳方法。在Python中,我來自哪裏,通常有一種「最好」(幾乎是標準的)做事方式。也許F#也是這樣,但我還沒有弄明白。所以我的問題是關於使用F#的最佳方式,而不是關於F#語法的技術問題。我應該編寫利用Intellisense的代碼嗎?

最近我看到Dr. Eric Meijer (C9 Lectures - Functional Programming Fundamentals Chapter 2 of 13)的一段視頻,其中Meijer博士稱讚OOP的點符號,注意到它允許Intellisense顯示可用方法的列表。他感嘆,這種設備在純FP中不可用,這使得程序員「前進」,從而使編程變得更加容易。

一些實驗表明,Intellisense當然可以使用F#類,但也可以與F#記錄一起使用,而F#記錄就像類一樣使用點符號。這意味着人們可以塑造自己的代碼,以便利用Intellisense而不用一路寫下類(我假設在F#類中比記錄更重且更慢,如果我錯了,請糾正我)。

下面的代碼演示了編寫代碼兩種方式(稱他們爲「版本」)執行相同的操作:

// Create a record type with two values that are functions of two arguments 
type AddSub = {add2: int -> int -> int; sub2: int -> int -> int} 

// Instantiate a record 
let addsub a = 
    {add2 = (fun x y -> a + x + y); sub2 = (fun x y -> a - x - y)} 

// Returns 7, Intellisense works on (addsub 0). 
(addsub 0).add2 3 4 
// Returns 3, Intellisense works on (addsub 10). 
(addsub 10).sub2 3 4 

// Create two functions of three arguments 
let add3 a x y = a + x + y 
let sub3 a x y = a - x - y 

// Also got 7, no Intellisense facility here 
add3 0 3 4 
// Also got 3, no Intellisense facility here 
sub3 10 3 4 

這表明,有純FP和OOP之間的中間策略:創建記錄類型具有函數值,如上所述。這樣的策略將我的代碼組織成以對象(記錄實例)爲中心的有意義的單元,並允許我使用Intellisense,但缺乏類提供的某些功能,如繼承和子類多態(如果我在此錯誤,請再糾正一次)。

來自OOP背景我覺得如果上面代碼中的a這樣的對象在某種程度上比參數x和y更「顯着」(我將使該術語未定義),那麼這樣的編碼策略是合理的,兩者都是合理的基於代碼組織和使用Intellisense的能力。另一方面,面對面向對象的複雜性,我寧願留在「純粹的」FP領域。

是否使用兩種極端替代方法(OOP和純FP)之間的有價值的折衷?

一般來說,給定三種備選方案(純FP,上述記錄或類別),關於一種替代方案優於其他方案的情況的一般準則是什麼?

最後,有沒有其他編碼策略可以幫助我組織代碼和/或利用Intellisense?

回答

8

智能感知在F#中仍然可以正常工作,但在模塊級而不是在課堂級。也就是說,我剛纔輸入List.,一旦我輸入點,VS代碼(與Ionide插件提供F#的智能感知)給了我儘可能完整的列表:appendaverageaverageBychoosechunkBySize ......

要拿到受益於自己的函數,把它們放在一個模塊中:

module AddSub = 
    let add2 x y = x + y 
    let sub2 x y = x - y 
    let add3 a x y = a + x + y 
    let sub3 a x y = a - x - y 

現在當你輸入AddSub.,您鍵入的點之後,智能感知會建議add2add3sub2sub3儘可能followups。然而,你已經保持你的功能「乾淨」和可口,在「適當的」F#風格。

最後,有關功能設計的另一條建議。您提到具有函數,其中一個參數(如add3sub3函數中的a)在某種程度上比其他參數更「顯着」。在F#中,任何這樣的參數應該可能是最後參數,因爲它允許你使用|>運營商把它放在一個函數鏈,就像這樣:

let a = 20 
a |> AddSub.add3 5 10 |> AddSub.sub3 2 3 // Result: 30 

或者更確切地說,使用樣式,大多數人喜歡當有一個從單一的起始值操作的「管道」:

let a = 20 
a 
|> AddSub.add3 5 10 
|> AddSub.sub3 2 3 
// Result: 30 

排隊管道當有更垂直的操作變得更加重要。我的經驗法則是,如果管道中指定的總額超過兩個「額外」參數(上面的管道有四個「額外」參數,其中兩個分別用於add3sub3函數),或者如果有任何「額外」參數比單個值更復雜(例如,如果一個參數是諸如(fun x -> sprintf "The value of x was %d" x)之類的匿名函數),那麼您應該垂直排列它。

P.S.如果您還沒有閱讀,請閱讀Scott Wlaschin關於Thinking Functionally的優秀系列。這將有助於解釋許多關於這個答案的事情,就像爲什麼我建議把「最重要的」論點放在最後。如果您沒有立即理解我的簡短評論,說明如何使它與|>參數一起使用,或者有其他任何令您困惑的答案,那麼您可能會從Scott的文章中獲得很多好處。

+0

太棒了!謝謝。我應該想到模塊。我猜Meijer博士抱怨說無法使用Intellisense,結果讓我失望了。但他在談論Haskell,也許在Haskell中沒有模塊或類似的設施。 – Soldalma

+0

是的,Haskell的標準做法是從模塊中導入函數,而不是使用它們,因此這不是對Intellisense友好的。 – Tarmil

+0

@Tarmil afaik,現在有用於Haskell的自動完成工具https://github.com/rikvdkleij/intellij-haskell – Yawar

3

你的問題很廣泛,模塊的一些具體方面已經被@munn覆蓋了。我想添加一些想法,這些想法是由我自己的工作在一個非常大的F#代碼庫上發生的,其中包含了不斷變化的開發團隊。

代碼發現規則。能夠看到(通過Intellisense)隨着代碼庫的增長,哪些方法可用於對象變得越來越重要。但更重要的是,它可以幫助你的團隊的新加入者,他們可能還不知道模塊X有所有的方法來處理class/record/etc的實例。

我發現F# component design guide非常有幫助。它提供了很多關於如何在面向對象和功能之間取得平衡的細節。對於您的具體問題,請參閱section on intrinsic operations,它在說明中直接引用您提出的要點:

對類型內在操作使用屬性和方法。 這是因爲某些來自函數式編程背景的人避免使用面向對象編程,所以更喜歡包含一組定義與某個類型相關的內部函數(例如,長度foo而非foo.Length)的函數的模塊。但請參閱下一個項目符號。一般來說,在F#中,使用面向對象的編程作爲軟件工程設備是首選。此策略還提供了一些工具優勢,例如Visual Studio的「智能感知」功能,通過「點入」對象來發現類型上的方法。

當試圖編寫類層次結構時,請考慮兩次是否無法用區分的聯合類型替換繼承。您可以使用記錄,課程等方式添加成員(靜態或實例成員):

type Animal = 
    | Cat | Dog 
    member this.Sound = match this with | Cat -> "meow" | Dog -> "bark" 
    static member FromString s = function | "cat" -> Cat | "dog" -> Dog | _ -> failwith "nope." 
+3

Imho,最好保持類型儘可能簡單,並將類型和操作函數放在一起模塊,並保持模塊緊緊圍繞其主要類型。然後你堅持使用各處的函數,而不必在OOP和FP樣式之間進行不明確的切換。見例如https://gist.github.com/yawaramin/552c25a23549f15556d54954e39f946d – Yawar

+0

@安頓 - 感謝您的見解。我承認你引用的建議讓我感到有點驚訝。從實際角度來看,這聽起來非常明智,但我預計在FP中有一些明智的選擇。 在類層次結構上,我不太確定。我遠不是一個專家,但是在我看來,你的動物類型必須在每次有新動物(獅子/吼叫)進入現場時進行修改。我相信這會違反SOLID中的開放/關閉原則。儘管如此,當我知道不存在新亞型的可能性時,這仍然有效。 – Soldalma

相關問題