如何避免多次迭代作为模式?

Eri*_*son 4 theory f# functional-programming

在函数式语言中(使用F#),我正在努力在功能组合的优点与单一责任之间找到平衡,并在序列上获得单次迭代的性能.实现两者的任何代码模式建议/示例?

我没有扎实的计算理论背景,我一遍又一遍地遇到这种一般模式:迭代一个集合,想要在迭代时做副作用,以避免在同一个集合或其结果集上进一步迭代.

一个典型的例子是"减少"或"过滤"功能:过滤时有很多次我希望根据过滤器的结果采取额外的步骤,但我想避免过滤结果的第二次枚举.

我们将输入验证作为一个简单的问题陈述:

  1. 命名输入数组
  2. 管道传输到"isValid"功能过滤器
  3. 副作用:记录无效的输入名称
  4. 管道有效输入以进一步执行

问题示例

在F#中,我可能最初写道:

inputs
// how to log invalid or other side-effects without messing up isValid??
|> Seq.filter isValid
|> execution
Run Code Online (Sandbox Code Playgroud)

解决方案示例#1

有了内联副作用,我需要这样的东西:

inputs
|> Seq.filter (fun (name,value) -> 
    let valid = isValid (name,value)
    // side-effect
    if not valid then
        printfn "Invalid argument %s" name
    valid
|> execution
Run Code Online (Sandbox Code Playgroud)

解决方案示例#2

我可以使用元组进行更纯粹的关注点分离,但需要第二次迭代:

let validationResults =
    inputs
    // initial iteration
    |> Seq.filter (fun (name,value) -> 
        let valid = isValid (name,value)
        (name,value,valid)
    |> execution

// one example of a 2nd iteration...
validationResults
|> Seq.filter (fun (_,_,valid) -> not valid)
|> Seq.map (fun (name,_,_) -> printfn "Invalid argument %s" name)
|> ignore

// another example of a 2nd iteration...
for validationResult in validationResults do
    if not valid then
        printfn "Invalid argument %s" name
Run Code Online (Sandbox Code Playgroud)

每个答案更新2014-07-23

我用它作为答案的解决方案.模式是使用包含条件的聚合函数.可能还有更优雅简洁的表达方式......

open System

let inputs = [("name","my name");("number","123456");("invalid","")]

let isValidValue (name,value) =
    not (String.IsNullOrWhiteSpace(value))

let logInvalidArg (name,value) =
    printfn "Invalid argument %s" name

let execution (name,value) =
    printfn "Valid argument %s: %s" name value

let inputPipeline input =
    match isValidValue input with
    | true -> execution input
    | false -> logInvalidArg input

inputs |> Seq.iter inputPipeline
Run Code Online (Sandbox Code Playgroud)

Mar*_*ann 5

在跟进我的其他回答有关记录和F#等副作用的成分,在这个例子中,你可以写日志记录更高级别的功能,就像这样:

let log f (name, value) =
    let valid = f (name, value)
    if not valid then
        printfn "Invalid argument %s" name
    valid
Run Code Online (Sandbox Code Playgroud)

它有这个签名:

f:(string * 'a -> bool) -> name:string * value:'a -> bool
Run Code Online (Sandbox Code Playgroud)

所以你现在可以用这样的'真实' isValid函数组合它:

inputs
|> Seq.filter (log isValid)
|> execution
Run Code Online (Sandbox Code Playgroud)

由于isValid函数具有签名,name:'a * value:int -> bool因此它适合函数的f参数log,您可以部分应用上述日志函数.