我应该何时使用函数中的函数而不是单独的私有函数?
我观察到我写的函数相当长:
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 :(得分: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
为参数的函数。作为一种功能,它看起来在许多不同的功能中都很有用。我的判断:分开。sourceX
和sourceY
- 这些是变量,而不是函数,但您可以将match
转换为一个名为source
的函数,它返回一个元组,然后调用它在optionsFor
函数中设置sourceX
和sourceY
的值。在我看来,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
)的好处。