我刚刚开始使用F#并试图了解典型的idoms和有效的思考和工作方式。
手头的任务是将制表符分隔文件简单转换为以逗号分隔的文件。典型的输入行将如下所示:
let line = "@ES# 01/31/2006 13:31:00 1303.00 1303.00 1302.00 1302.00 2514 0"
我开始使用这样的循环代码:
// inFile and outFile defined in preceding code not shown here
for line in File.ReadLines(inFile) do
let typicalArray = line.Split '\t'
let transformedLine = typicalArray |> String.concat ","
outFile.WriteLine(transformedLine)
然后我用一个Regex.Replace()替换了split / concat操作对:
for line in File.ReadLines(inFile) do
let transformedLine = Regex.Replace(line, "\t",",")
outFile.WriteLine(transformedLine)
现在,最后,用管道替换了循环:
File.ReadLines(inFile)
|> Seq.map (fun x -> Regex.Replace(x, "\t", ","))
|> Seq.iter (fun y -> outFile.WriteLine(y))
// other housekeeping code below here not shown
虽然所有版本都有效,但最终版本对我来说最直观。这是一个更有经验的F#程序员如何完成这项任务?
答案 0 :(得分:11)
我认为这三个版本都是F#专家写的非常好的惯用代码。
如果他们让我解决我遇到的问题,我通常更喜欢使用内置语言功能(如for
循环和if
条件)编写代码。这些是必要的,但我认为当API需要命令性代码(如outFile.WriteLine
)时使用它们是个好主意。正如你所提到的 - 你从这个版本开始(我也会这样做)。
使用高阶函数也很好 - 虽然我可能只在我想编写数据 transformation 并获得一个新的序列或行列表时才会这样做 - 如果你是这样的话会很方便使用File.WriteAllLines
而不是逐行编写行。虽然,也可以通过简单地用序列表达式包装第二个版本来完成:
let transformed =
seq { for line in File.ReadLines(inFile) -> Regex.Replace(line, "\t",",") }
File.WriteAllLines(outFilePath, transformed)
我认为没有任何客观理由可以选择其中一个版本。我个人的风格偏好是使用for
并重构序列表达式(如果需要),但其他人可能不同意。
答案 1 :(得分:0)
请注意,如果要写入要读取的同一文件,则需要记住Seq正在执行惰性评估。
使用数组而不是Seq可以确保在需要写入时关闭文件以供读取。
这有效:
let lines =
file |> File.ReadAllLines
|> Array.map(fun line -> ..modify line..)
File.WriteAllLines(file, lines)
这不是(导致文件访问文件冲突)
let lines =
file |> File.ReadLines
|> Seq.map(fun line -> ..modify line..)
File.WriteAllLines(file, lines)
(可能与另一个讨论here重叠,其中中间变量有助于解决同一问题)