在函数式语言中(使用F#),我努力在功能组合的优点与单一责任之间找到平衡,并在序列上获得单次迭代的性能。任何代码模式建议/示例都可以实现两者?
我没有扎实的计算理论背景,我一遍又一遍地遇到这种一般模式:迭代一个集合,想要在迭代时做副作用,以避免在同一个集合或其结果集上进一步迭代
一个典型的例子是“减少”或“过滤”功能:过滤时有很多次我希望根据过滤器的结果采取额外的步骤,但我想避免过滤的第二次枚举结果
让我们将输入验证作为一个简单的问题陈述:
在F#中,我最初可能会写:
inputs
// how to log invalid or other side-effects without messing up isValid??
|> Seq.filter isValid
|> execution
有了内联副作用,我需要这样的东西:
inputs
|> Seq.filter (fun (name,value) ->
let valid = isValid (name,value)
// side-effect
if not valid then
printfn "Invalid argument %s" name
valid
|> execution
我可以使用元组进行更纯粹的关注点分离,但需要第二次迭代:
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
我根据答案使用了这个解决方案。模式是使用包含条件的聚合函数。可能还有更优雅简洁的表达方式...
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
答案 0 :(得分:5)
关于F#中日志记录和其他副作用的组成my other answer,在本例中,您可以编写更高级别的日志记录功能,如下所示:
let log f (name, value) =
let valid = f (name, value)
if not valid then
printfn "Invalid argument %s" name
valid
它有这个签名:
f:(string * 'a -> bool) -> name:string * value:'a -> bool
所以你现在可以用真实的'来构建它。 isValid
的功能如下:
inputs
|> Seq.filter (log isValid)
|> execution
由于isValid
函数具有签名name:'a * value:int -> bool
,因此它适合f
函数的log
参数,并且您可以部分应用上述日志函数。
答案 1 :(得分:2)
这并没有解决你只关注迭代序列的问题(对于一个数组来说,无论如何都非常便宜),但我认为,更容易阅读和更清楚:
let valid, invalid = Array.partition isValid inputs
for name, _ in invalid do printfn "Invalid argument %s" name
execution valid