我的F#代码中的StackOverflow异常

时间:2013-11-22 19:55:30

标签: f# tail-recursion

我正在寻找一组程序集中的特定方法。为了找到我的方法,我写了这段代码

module MethodNameExtractor

open System
open System.IO
open System.Reflection

let ListMethodsInAssembly (input : AssemblyName) =   
  try
    let appd = AppDomain.CreateDomain("temp")
    try    
      let assm = Assembly.Load(input)
      assm.GetTypes() 
      |> Array.toSeq
      |> Seq.fold (fun accm ty -> Seq.append accm (ty.GetMethods() |> Array.toSeq)) Seq.empty
      |> Seq.filter (fun x -> x.Name <> "GetType" && x.Name <> "ToString" && x.Name <> "GetHashCode" && x.Name <> "GetType") 
    finally
      AppDomain.Unload(appd)
  with
  | :? System.Exception -> Seq.empty

let ListAllDlls = 
  let rec expandDir (parent : DirectoryInfo) accm =    
    let files = parent.GetFiles() |>  Array.toSeq |> Seq.map (fun f -> try Some(AssemblyName.GetAssemblyName f.FullName) with | :? System.Exception -> None)
    let accm = Seq.append files accm
    let subDirs = parent.GetDirectories() |> Array.toSeq
    Seq.fold (fun accm d -> expandDir d accm) accm subDirs

  let basePath = "c:\\Windows\\Microsoft.NET\\assembly\\"
  let loc = ["GAC_64"; "GAC_MSIL";] |> List.toSeq  
  Seq.fold (fun acc l ->
      let path = basePath + l
      expandDir (new DirectoryInfo(path)) acc
    ) 
    Seq.empty
    loc


[<EntryPoint>]
let main args =   
  ListAllDlls 
  |> Seq.fold (
      fun accm x -> 
        match x with 
        | Some(a) -> Seq.append accm (ListMethodsInAssembly a)
        | None -> accm
    ) Seq.empty  
  |> Seq.iter (fun x -> printfn "%s" x.Name)
  0

我的希望是因为我使用的是Sequences和Tail Recursion ....我不应该消耗太多内存,如果我可以让我的代码运行得足够长,我可以获得所有程序集中的方法列表。

我的代码非常简单...获取所有目录和子目录中的DLL列表。

加载DLL并获取类型...然后从类型中获取所有公共方法...

但代码处理大约200万条记录,然后获得StackOverflow异常。

我已经在使用Tail递归和Sequence ...如何进一步改进此代码?

2 个答案:

答案 0 :(得分:3)

此函数不是尾递归的:

let ListAllDlls = 
  let rec expandDir (parent : DirectoryInfo) accm =    
    let files = parent.GetFiles() |>  Array.toSeq |> Seq.map (fun f -> try Some(AssemblyName.GetAssemblyName f.FullName) with | :? System.Exception -> None)
    let accm = Seq.append files accm
    let subDirs = parent.GetDirectories() |> Array.toSeq
    Seq.fold (fun accm d -> expandDir d accm) accm subDirs

请注意,对expandDir的递归调用的结果在调用函数中使用(特别是在传递给Seq.fold的函数中。这可以防止尾递归,因为调用堆栈帧仍然在使用时进行递归调用。

这是一个树递归,因此没有一种很好的方法可以使尾递归。有关使用继续传递解决此问题的信息,请参阅here。继续传递的本质是维护你尚未作为lambda使用的分支列表。

答案 1 :(得分:3)

您的代码存在一些问题:

  • Seq.foldSeq.appendSeq.empty的组合在序列表达式中的Seq.collectyield!执行效果不佳。
  • 此处不需要选项,只需跳过序列表达式中的生成或使用Seq.choose。
  • 将序列上的函数应用于列表/数组时,不需要Array.toSeqList.toSeq
    • try ... with | :? System.Exception可缩短为try ... with _

您的expandDir函数不是尾递归的。如果将其更改为使用序列表达式,则会使用CPS进行优化,因此不会发生StackOverflow。

这是一个改进版本。我认为它更具可读性,可能更快。

let ListMethodsInAssembly (input : AssemblyName) =   
  try
    let appd = AppDomain.CreateDomain("temp")
    try    
      let assm = Assembly.Load(input)
      assm.GetTypes()
      |> Seq.collect (fun ty -> ty.GetMethods())
      |> Seq.filter (fun x -> x.Name <> "GetType" && x.Name <> "ToString" && x.Name <> "GetHashCode" && x.Name <> "GetType") 
    finally
      AppDomain.Unload(appd)
  with _ -> Seq.empty

let ListAllDlls = 
  let rec expandDir (parent : DirectoryInfo) = 
    let getAssemblyNameFromFile (f : FileInfo) =   
        try 
           [ AssemblyName.GetAssemblyName f.FullName ]
        with _ -> []
    seq {
        for f in parent.GetFiles() do
            yield! getAssemblyNameFromFile f
        for subDir in parent.GetDirectories() do
            yield! expandDir subDir
    }

  let basePath = "c:\\Windows\\Microsoft.NET\\assembly\\"
  let loc = ["GAC_64"; "GAC_MSIL";]
  seq {
    for l in loc do  
        let path = basePath + l
        yield! expandDir (DirectoryInfo(path))
  }

[<EntryPoint>]
let main args =   
  ListAllDlls 
  |> Seq.collect ListMethodsInAssembly
  |> Seq.iter (fun x -> printfn "%s" x.Name)
  0