簡體   English   中英

F#:將頂級函數轉換為成員方法的優勢?

[英]F#: Advantages of converting top-level functions to member methods?

之前我曾就我的第一個F#項目請求了一些反饋。 在結束問題之前,因為范圍太大,有人很友好地查看並留下一些反饋。

他們提到的一件事是指出我有許多常規函數可以轉換為我數據類型的方法。 我盡職盡責地改變了這樣的事情

let getDecisions hand =
    let (/=/) card1 card2 = matchValue card1 = matchValue card2
    let canSplit() = 
        let isPair() =
            match hand.Cards with
            | card1 :: card2 :: [] when card1 /=/ card2 -> true
            | _ -> false
        not (hasState Splitting hand) && isPair()
    let decisions = [Hit; Stand]
    let split = if canSplit() then [Split] else []
    let doubleDown = if hasState Initial hand then [DoubleDown] else []
    decisions @ split @ doubleDown

對此:

type Hand
// ...stuff...
member hand.GetDecisions =
    let (/=/) (c1 : Card) (c2 : Card) = c1.MatchValue = c2.MatchValue
    let canSplit() = 
        let isPair() =
            match hand.Cards with
            | card1 :: card2 :: [] when card1 /=/ card2 -> true
            | _ -> false
        not (hand.HasState Splitting) && isPair()
    let decisions = [Hit; Stand]
    let split = if canSplit() then [Split] else []
    let doubleDown = if hand.HasState Initial then [DoubleDown] else []
    decisions @ split @ doubleDown

現在,我不懷疑我是一個白痴,但除了(我猜)讓C#interop變得更容易,這是什么讓我受益? 具體來說,我發現一對夫婦DIS優勢,不計算轉換(我不會算,因為我可以通過這種方式在第一時間已經做了額外的工作,我認為,盡管這已經使用F#互動更多的做痛苦)。 首先,我現在不再能夠輕松地使用“流水線”功能了。 我不得不去改變some |> chained |> calls (some |> chained).Calls等。而且,它似乎使我的類型系統變硬 - 而在我的原始版本中,我的程序不需要類型注釋,之后在很大程度上轉換為成員方法,我得到了一堆關於查找在這一點上不確定的錯誤,我不得不去添加類型注釋(這個例子在上面的(/=/) )。

我希望我沒有太過可疑,因為我很欣賞我收到的建議,而寫慣用代碼對我來說很重要。 我只是好奇為什么這個成語是這樣的:)

謝謝!

我在F#編程中使用的一種做法是在兩個地方使用代碼。

實際的實現很自然地放在一個模塊中:

module Hand = 
  let getDecisions hand =
    let (/=/) card1 card2 = matchValue card1 = matchValue card2
  ...

並在成員函數/屬性中給出一個“鏈接”:

type Hand
  member this.GetDecisions = Hand.getDecisions this

這種做法通常也用於其他F#庫,例如powerpack中的矩陣和向量實現matrix.fs。

在實踐中,並非每個功能都應放在兩個地方。 最終決定應基於領域知識。

關於頂層,實踐是將函數放入模塊中,並在必要時將其中一些放在頂層。 例如,在F#PowerPack中, Matrix<'T>位於命名空間Microsoft.FSharp.Math 在頂層中為矩陣構造函數創建快捷方式很方便,這樣用戶可以直接構造矩陣而無需打開命名空間:

[<AutoOpen>]
module MatrixTopLevelOperators = 
    let matrix ll = Microsoft.FSharp.Math.Matrix.ofSeq ll

成員的一個優點是智能感知和其他工具,使成員可被發現。 當用戶想要探索對象foo ,他們可以鍵入foo. 並獲取該類型的方法列表。 會員們的“規模”也更容易,因為你不會在頂層浮動數十個名字; 隨着程序大小的增加,您需要更多名稱才能在限定時使用(someObj.Method或SomeNamespace.Type或SomeModule.func,而不僅僅是Method / Type / func'floating free')。

如你所見,也有缺點; 類型推斷尤其值得注意(你需要知道x先驗的類型,才能調用x.Something ); 在非常普遍使用的類型和功能的情況下,提供成員和功能模塊可能是有用的,以獲得兩者的好處(例如,對於FSharp.Core中的常見數據類型會發生什么)。

這些是“腳本方便”與“軟件工程規模”的典型權衡。 我個人總是傾向於后者。

這是一個棘手的問題,這兩種方法都有優點和缺點。 通常,在編寫更多功能代碼時,我傾向於使用成員編寫更多面向對象/ C#樣式的代碼和全局函數。 但是,我不確定我能說清楚它的區別。

我更喜歡在編寫時使用全局函數:

  • 功能數據類型,例如樹/列表和其他通常可用的數據結構,可在程序中保留大量數據。 如果您的類型提供了一些表示為高階函數(例如List.map )的操作,那么它可能就是這種數據類型。

  • 不與類型相關的功能 - 在某些操作不嚴格屬於某個特定類型的情況下。 例如,當您對某些數據結構有兩個表示,並且您正在實現兩者之間的轉換時(例如,在編譯器中鍵入AST和無類型AST)。 在這種情況下,功能是更好的選擇。

另一方面,我會在寫作時使用成員:

  • 簡單類型 ,如Card ,可以具有屬性(如值和顏色)和相對簡單(或沒有)的方法執行一些計算。

  • 面向對象 - 當您需要使用面向對象的概念(如接口)時,您需要將功能編寫為成員,因此(事先)考慮是否適用於您的類型可能會很有用。

我還要提到,在應用程序中使用成員來處理某些簡單類型(例如Card )並使用頂級函數實現應用程序的其余部分是完全正常的。 事實上,我認為這可能是您遇到的最佳方法。

值得注意的是,還可以創建一種類型,該類型具有成員以及提供與功能相同的功能的模塊。 這允許庫的用戶選擇他/她喜歡的樣式,這是完全有效的方法(但是,它可能對獨立庫有意義)。

作為旁注,可以使用成員和函數來實現對調用的鏈接:

// Using LINQ-like extension methods
list.Where(fun a -> a%3 = 0).Select(fun a -> a * a)
// Using F# list-processing functions
list |> List.filter (fun a -> a%3 = 0) |> List.map (fun a -> a * a)

我通常主要是因為成員削弱類型推理的方式而避免使用類。 如果你這樣做,有時候使模塊名稱與類型名稱相同是很方便的 - 下面的編譯器指令派上用場。

type Hand
// ...stuff...

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Hand =
    let GetDecisions hand = //...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM