包装递归函数以计算函数调用的数量

时间:2010-11-13 15:50:33

标签: f#

假设我有一个递归函数,我想知道函数每个输入值调用了多少次。而不是放置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)]

2 个答案:

答案 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函数中(因此当它自己调用时,它将调用带有计数支持的版本)。