10

我刚刚开始使用 F# 并试图了解典型的习惯以及有效的思维和工作方式。

手头的任务是将制表符分隔的文件简单地转换为逗号分隔的文件。典型的输入行如下所示:

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# 程序员完成这项任务的方式吗?

4

2 回答 2

12

我认为所有三个版本都非常好,F# 专家会编写的惯用代码。

如果它们能让我解决我遇到的问题,我通常更喜欢使用内置语言功能(如for循环和条件)编写代码。if这些是命令式的,但我认为当 API 需要命令式代码(如outFile.WriteLine)时使用它们是一个好主意。正如您所提到的 - 您从这个版本开始(我也会这样做)。

使用高阶函数也很好——尽管我可能只会在我想编写数据转换并获得新的序列或行列表时这样做——如果您使用File.WriteAllLines而不是逐行编写行,这将很方便. 虽然,这也可以通过简单地用序列表达式包装你的第二个版本来完成:

let transformed = 
    seq { for line in File.ReadLines(inFile) -> Regex.Replace(line, "\t",",") }
File.WriteAllLines(outFilePath, transformed) 

我认为没有任何客观理由偏爱其中一个版本。我个人的风格偏好是使用for和重构来排序表达式(如果需要),但其他人可能会不同意。

于 2013-06-30T21:05:09.723 回答
0

附带说明,如果你想写入你正在读取的同一个文件,你需要记住 Seq 正在执行惰性评估。

使用 Array 而不是 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)

(可能与此处的另一个讨论重叠,其中中间变量有助于解决相同的问题)

于 2019-03-19T10:01:05.997 回答