我是F#的新手(ish),我正在尝试获取文件系统目录的树形图。这就是我想出的:
type FSEntry =
| File of name:string
| Directory of name:string * entries:seq<FSEntry>
let BuildFSDirectoryTreeNonTailRecursive path =
let rec GetEntries (directoryInfo:System.IO.DirectoryInfo) =
directoryInfo.EnumerateFileSystemInfos("*", System.IO.SearchOption.TopDirectoryOnly)
|> Seq.map (fun info ->
match info with
| :? System.IO.FileInfo as file -> File (file.Name)
| :? System.IO.DirectoryInfo as dir -> Directory (dir.Name, GetEntries dir)
| _ -> failwith "Illegal FileSystemInfo type"
)
let directoryInfo = System.IO.DirectoryInfo path
Directory (path, GetEntries directoryInfo)
但是......很确定这不是尾递归。我看了一下生成的IL并没有看到任何尾部前缀。有一个更好的方法吗?我尝试使用累加器,但没有看到它有多大帮助。我尝试了相互递归函数,无处可去。也许继续可行,但我发现这令人困惑。
(我知道在这种特殊情况下,堆栈深度不会是的问题但是仍然想知道如何解决这种非尾递归问题)
OTOH,似乎确实奏效了。以下打印出我期待的内容:let PrintFSEntry fsEntry =
let rec printFSEntryHelper indent entry =
match entry with
| File name -> printfn "%s%s" indent name
| Directory(name, entries) ->
printfn "%s\\%s" indent name
entries
|> Seq.sortBy (function | File name -> 0 | Directory (name, entries) -> 1)
|> Seq.iter (printFSEntryHelper (indent + " "))
printFSEntryHelper "" fsEntry
这应该是一个不同的问题,但是......如何测试BuildFSDirectoryTreeNonTailRecursive?我想我可以像在C#中那样创建一个界面并模拟它,但我认为F#有更好的方法。
已编辑:根据初始评论,我指定我知道堆栈空间可能不是问题。我还指出我主要关注测试第一个函数。
答案 0 :(得分:1)
要扩展我之前的评论 - 除非你预期使用会导致堆栈溢出而没有尾递归的输入,否则从函数尾递归中无法获得任何东西。对于您的情况,限制因素是路径名中的~260个字符,超过该字符,大多数Windows API将开始中断。在由于非尾递归而开始耗尽堆栈空间之前,你会遇到这种情况。
对于测试,您希望您的功能尽可能接近纯函数。这涉及重构副作用的功能部分。这两种函数都是这种情况 - 其中一种隐式依赖于文件系统,另一种直接将文本打印到标准输出。
我想我所建议的重构与Mark Seemann的观点相当接近:几个模拟 - 检查,接口很少 - 检查,功能组合 - 检查。然而,你所拥有的例子并没有很好地适应它,因为它是EnumerateFileSystemInfo
之上极薄的贴面。我可以像这样摆脱System.IO:
type FSInfo = DirInfo of string * string | FileInfo of string
let build enumerate path =
let rec entries path =
enumerate path
|> Seq.map (fun info ->
match info with
| DirInfo (name, path) -> Directory(name, entries path)
| FileInfo name -> File name)
Directory(path, entries path)
现在我留下了一个enumerate: string -> seq<FSInfo>
函数,可以轻松替换为甚至不触及驱动器的测试实现。然后枚举的默认实现是:
let enumerateFileSystem path =
let directoryInfo = DirectoryInfo(path)
directoryInfo.EnumerateFileSystemInfos("*", System.IO.SearchOption.TopDirectoryOnly)
|> Seq.map (fun info ->
match info with
| :? System.IO.FileInfo as file -> FileInfo (file.Name)
| :? System.IO.DirectoryInfo as dir -> DirInfo (dir.Name, dir.FullName)
| _ -> failwith "Illegal FileSystemInfo type")
你可以看到它与build
函数具有几乎相同的形状,减去递归,因为逻辑的整个“核心”都在EnumerateFileSystemInfos
之内,它超出了你的代码。这是一个小小的改进,不是以任何方式测试引起的损坏,但它仍然不会很快成为任何人的幻灯片。