2016-08-02 104 views
0

什麼時候應該在函數中使用函數而不是單獨的私有函數?什麼時候應該在函數中使用函數而不是單獨的私有函數?

我觀察到我寫的函數是相當長:

let optionsFor piece (positions:Space list) = 

    let yDirection = match piece with 
        | Black _ -> -1 
        | Red _ -> 1 

    let sourceX , sourceY = 
     match piece with 
     | Black (checker , pos) -> pos 
     | Red (checker , pos) -> pos 

    let optionsForPiece = 
     (fun pos -> pos = ((sourceX - 1) , (sourceY + yDirection)) || 
        pos = ((sourceX + 1) , (sourceY + yDirection))) 

    let availableSelection = 
     (fun space -> match space with 
         | Available pos -> Some pos 
         | Allocated _ -> None) 

    let availablePositions = 
     positions |> List.filter toAvailable 
        |> List.choose availableSelection 

    availablePositions |> List.filter optionsForPiece 

因此,我認爲重構上面的功能成幾個小的功能。

但是,我不確定這是否是函數式編程所必需的。

目前關於內部函數的建議與將其提取到私有函數有什麼不同?

附錄:

open NUnit.Framework 
open FsUnit 

(* Types *) 
type Black = BlackKing | BlackSoldier 
type Red = RedKing | RedSoldier 

type Coordinate = int * int 

type Piece = 
    | Black of Black * Coordinate 
    | Red of Red * Coordinate 

type Space = 
    | Allocated of Piece 
    | Available of Coordinate 

type Status = 
    | BlacksTurn | RedsTurn 
    | BlackWins | RedWins 

(* Functions *) 
let black coordinate = Allocated (Black (BlackSoldier , coordinate)) 
let red coordinate = Allocated (Red (RedSoldier , coordinate)) 

let startGame() = 
    [ red (0,0); red (2,0); red (4,0); red (6,0) 
     red (1,1); red (3,1); red (5,1); red (7,1) 
     red (0,2); red (2,2); red (4,2); red (6,2) 

     Available (1,3); Available (3,3); Available (5,3); Available (7,3) 
     Available (0,4); Available (2,4); Available (4,4); Available (6,4) 

     black (1,5); black (3,5); black (5,5); black (7,5) 
     black (0,6); black (2,6); black (4,6); black (6,6) 
     black (1,7); black (3,7); black (5,7); black (7,7) ] , BlacksTurn 

let private toAvailable = 
    (fun space -> match space with 
        | Available pos -> true 
        | _    -> false) 

let available (positions:Space list) = positions |> List.filter toAvailable 

let optionsFor piece (positions:Space list) = 

    let yDirection = match piece with 
        | Black _ -> -1 
        | Red _ -> 1 

    let sourceX , sourceY = 
     match piece with 
     | Black (checker , pos) -> pos 
     | Red (checker , pos) -> pos 

    let optionsForPiece = 
     (fun pos -> pos = ((sourceX - 1) , (sourceY + yDirection)) || 
        pos = ((sourceX + 1) , (sourceY + yDirection))) 

    let availableSelection = 
     (fun space -> match space with 
         | Available pos -> Some pos 
         | Allocated _ -> None) 

    let availablePositions = 
     positions |> List.filter toAvailable 
        |> List.choose availableSelection 

    availablePositions |> List.filter optionsForPiece 
+0

我不知道這是否更適合[Code Review](http://codereview.stackexchange.com/)。 – s952163

+1

我的代碼只是用作上下文。我認爲這個問題甚至不需要代碼。我想我正在尋找每個選項的優點和缺點。 –

+0

我不完全確定這是否是你所要求的,但是這應該由你的使用決定,如果你想/從外部訪問函數(函數和你的模塊以及其他.NET代碼)。定義許多小函數和內部函數沒有什麼不妥,只要你組織它們。 – s952163

回答

4

這是更加的意見爲主,但我會提供我的意見。

我的經驗法則是,如果「助手」功能是高度關聯與「主」功能,我會寫它作爲嵌套功能。如果它們沒有緊密關聯,我會將輔助函數編寫爲一個單獨的函數 - 我甚至可能不會將它私有化,因爲您永遠不知道它何時可以派上用場,以供其他模塊中的其他代碼使用。

一個緊密關聯的例子內函數將是循環與累加器函數,你通常會在遞歸函數編程中寫入。例如,這裏的一些代碼,我寫了一個F#編程練習:

module BinarySearchTree 

type Node<'T> = 
    { left: Node<'T> option 
     value: 'T 
     right: Node<'T> option } 

let singleton v = { left = None; value = v; right = None } 

let rec insert v t = 
    if v <= t.value 
     then match t.left with 
      | None -> { t with left = singleton v |> Some } 
      | Some n -> { t with left = insert v n |> Some } 
     else match t.right with 
      | None -> { t with right = singleton v |> Some } 
      | Some n -> { t with right = insert v n |> Some } 

let fromList l = 
    match l with 
    | [] -> failwith "Can't create a tree from an empty list" 
    | hd::tl -> 
     tl |> List.fold (fun t v -> insert v t) (singleton hd) 

let toList t = 
    let rec loop acc = function 
     | None -> acc 
     | Some node -> 
      (loop [] node.left) @ (node.value :: (loop [] node.right)) 
    loop [] (Some t) 

看看去年toList功能。它有一個內部函數,我稱之爲loop,作爲一個獨立函數是沒有意義的。這是緊密toList函數關聯,它只是有意義的,保持它作爲一個內部功能,不可從外部toList訪問。

但是,當我編寫fromList函數時,我沒有在其中定義insert作爲內部函數。 insert功能本身是有用的,除了fromList的功能。所以我寫了insert作爲一個單獨的函數。儘管fromList是我的代碼中唯一實際使用insert,的函數,未來可能不一定是真的。我可能會寫一個fromArray函數,我不想爲了效率而重用fromList。 (我可能fromArraylet fromArray a = a |> List.ofArray |> fromList,但是這會創建一個不必要的列表,我只是在完成後就扔掉;更直接地迭代數組並將其作爲insert作爲效率更明智適當)。

因此,有一個例子說明在相同模塊中使用嵌套內部函數與單獨函數的明智之處。現在讓我們看看你的代碼。

  • yDirection - 這是一個變量,但也可以變成以piece作爲參數的函數。作爲一項功能,它看起來可能在許多不同的功能中有用。我的判斷:單獨
  • sourceXsourceY - 這些變量,而不是功能,但你可以把那match到一個名爲source返回一個元組函數,然後把它在你的optionsFor功能設置的sourceXsourceY值。在我看來,source函數最有意義的作爲單獨的函數。
  • optionsForPiece - 此函數看起來與optionsFor函數緊密關聯,因此您可能不想從別處調用它。我的判斷:嵌套
  • availableSelection - 這可能在幾種情況下非常有用,而不僅僅是optionsFor。我的判斷:單獨
  • availablePositions - 這是一個變量,但很容易變成一個函數,它將positions作爲參數並返回哪些是可用的。再次,這在幾種情況下可能有用。我的判斷:單獨

因此,通過拆分出所有看起來他們可以重新使用的功能,我們已經得到了你的optionsFor功能下降到以下幾點:

// Functions yDirection, source, availableSelection, 
// and availablePositions are all defined "outside" 

let optionsFor piece (positions:Space list) = 

    let yDir = yDirection piece 

    let sourceX , sourceY = source piece 

    let optionsForPiece pos = 
     pos = ((sourceX - 1) , (sourceY + yDir)) || 
     pos = ((sourceX + 1) , (sourceY + yDir)) 

    positions |> availablePositions |> List.filter optionsForPiece 

當你重溫這是一個很大更具可讀性代碼稍後,再加上您可以獲得更多可重用函數(如availableSelections)的好處,以便在您編寫代碼的下一位時使用。

相關問題