2016-08-02 110 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)的好处,以便在您编写代码的下一位时使用。

相关问题