我应该何时使用函数内的函数而不是单独的私有函数?

时间:2016-08-02 02:48:51

标签: f#

我应该何时使用函数中的函数而不是单独的私有函数?

我观察到我写的函数相当长:

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

1 个答案:

答案 0 :(得分:4)

这是基于意见的,但我会提出我的意见。

我的经验法则是,如果“helper”函数与“main”函数紧密关联,我会将其写为嵌套函数。如果它们没有紧密关联,我会将辅助函数编写为一个单独的函数 - 我甚至可能不会将其设置为私有,因为您永远不知道它何时可以派上用于其他模块中的其他代码。

紧密关联的内部函数的一个例子是循环累加器函数,你经常最终用递归函数编程编写。例如,这里是我为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。 (我可能fromArray写为let fromArray a = a |> List.ofArray |> fromList,但这会创建一个不必要的列表,当我完成时我只会扔掉它;它更有意义,效率更高-wise,直接迭代数组并根据需要调用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)的好处。