假设我有一个递归函数,我想知道函数每个输入值调用了多少次。而不是放置printf表达式或更改返回类型以包括调用次数,是否可以用另一个“包装”该函数来实现这个?我希望包装函数返回函数调用的数量和原始函数的结果。它应该可以在不同的功能中重复使用。
这是我所拥有的,但它不起作用。
open System
open System.IO
open System.Collections.Generic
/// example recursive function
let rec getfilenames dir =
seq {
yield Directory.GetFiles dir
for x in Directory.GetDirectories dir do yield! getfilenames x}
/// function to count the number of calls a recursive function makes to itself
let wrapped (f: 'a -> 'b) =
let d = new Dictionary<'a, int>()
fun x ->
let ok, res = d.TryGetValue(x)
if ok then d.[x] <- d.[x] + 1
else
d.Add(x, 1)
d, f x
> let f = wrapped getfilenames
let calls, res = f "c:\\temp";;
val f : (string -> Dictionary<string,int> * seq<string []>)
val res : seq<string []>
val calls : Dictionary<string,int> = dict [("c:\temp", 1)]
答案 0 :(得分:3)
这不起作用,因为getfilenames
被定义为调用getfilenames
,而不是任何其他函数,尤其是之后定义的函数。因此,只要您的包装器调用该函数,该函数就会忽略您的包装器并开始调用它自己。
您需要做的是将递归移出getfilenames
函数并移动到另一个函数中,方法是将函数作为参数递归调用。
let body recfun dir =
seq {
yield Directory.GetFiles dir
for x in Directory.GetDirectories dir do yield! recfun x}
let rec getfilenames dir = body getfilenames dir
现在,您可以在将body
插入递归函数之前将其包裹起来:
let wrap f =
let d = (* ... *) in
d, fun recfun x ->
let ok, res = d.TryGetValue(x)
if ok then d.[x] <- d.[x] + 1
else d.Add(x, 1)
f recfun x
let calls, counted_body = wrap body
let getfilenames dir = counted_body getfilenames dir
请注意,wrap
函数返回包装函数(具有与原始函数相同的签名)和字典,以进行外部访问。然后会在calls
中找到通话次数。
答案 1 :(得分:2)
正如Victor指出的那样,你不能采用递归函数并将一些行为“注入”到递归调用发生的地方(因为函数已经完成)。你需要为此提供一些扩展点。在Victor的解决方案中,这是通过将函数作为参数递归调用来完成的,这是最通用的解决方案。
更简单的选项是使用F#值递归,它允许您创建一个函数值并在其声明中使用它。您可以通过调用另一个向函数添加某些行为的函数(例如,计数)来使用它来创建递归函数:
let rec factorial = counted (fun x ->
if x = 0 then 1
else x * (factorial (x - 1)) )
factorial 10
在lambda函数中,我们可以直接访问我们定义的函数,因此不需要将函数作为附加参数递归调用。函数counted
只包含给定函数f
并添加一些功能:
let counted f =
let count = ref 0
(fun x ->
count := !count + 1;
printfn "call: %d" (!count)
f x)
由于值递归,该功能将被添加到factorial
函数中(因此当它自己调用时,它将调用带有计数支持的版本)。