只是从f#开始,我进入OO C#背景,并且我有以下代码读取uk邮政编码的文本文件,然后使用邮政编码将其打到api端点,我测试结果以查看post是否有效是否:
let postCodeFile = "c:\\tmp\\randomPostCodes.txt"
let readLines filePath = System.IO.File.ReadLines(filePath)
let lines = readLines postCodeFile
let postCodeValidDaterUrl = "https://api.postcodes.io/postcodes/"
let validatePostCode postCode =
Request.createUrl Get (postCodeValidDaterUrl + postCode)
|> getResponse
|> run
let translateResponse response =
match response.statusCode with
| 200 -> true
| _ -> false
let validPostCode = validatePostCode >> translateResponse
lines |> Seq.iter(fun x -> validPostCode(x) |> printfn "%s-%b" x)
关于使其更具功能性的任何建议?
答案 0 :(得分:4)
您已经使此功能尽可能地发挥了作用。我将仔细阅读您的代码,并解释为什么您的决定是正确的,在某些情况下,您可以在其中进行一些小的改进。
let postCodeFile = "c:\\tmp\\randomPostCodes.txt"
将此作为命名变量很好;在实际代码中,这当然是脚本的参数或函数参数。因此,将其作为命名变量很有用。
let readLines filePath = System.IO.File.ReadLines(filePath)
此功能不是严格必需的;由于System.IO.File.ReadLines
采用单个参数,因此您可以将其传递给它(例如postCodeFile |> System.IO.File.ReadLines |> Seq.iter(...)
。但是我喜欢较短的名称,所以我也可能会这样写。
let lines = readLines postCodeFile
我可能会放弃创建lines
名称,而是在代码的最后一行执行postCodeFile |> readLines |> Seq.iter (...)
。这样做的方式本质上没有错误,但是您没有在其他任何地方使用lines
变量,因此没有真正的理由为其命名。 F#的管道允许您跳过对中间步骤的命名。
let postCodeValidDaterUrl = "https://api.postcodes.io/postcodes/"
再次,给它起个好名字,以便以后可以将其转换为参数或配置文件变量。我在这里看到唯一可以改进的是拼写:ValidDater
应该是Validator
。
let validatePostCode postCode =
Request.createUrl Get (postCodeValidDaterUrl + postCode)
|> getResponse
|> run
看起来不错。
let translateResponse response =
match response.statusCode with
| 200 -> true
| _ -> false
可能更简单:let translateResponse response = (response.statusCode = 200)
。但是,如果您有一个API可以返回其他状态码(例如204)来表示成功,那么match
表达式可让您稍后对其进行扩展。我可能会在这里进行比较简单的比较,并仅在需要时添加match
语句。
let validPostCode = validatePostCode >> translateResponse
很好。
lines |> Seq.iter(fun x -> validPostCode(x) |> printfn "%s-%b" x)
正如我前面提到的,lines
是一个中间步骤,所以我可能会将其更改为postCodeFile |> readLines |> Seq.iter (...)
,因为这可以让您跳过为中间步骤命名的方法。
您在这里做得很好的两件事:
您编写的每个函数仅做一件事情,然后将这些函数组合在一起以创建更大的代码“构建块”。例如,validatePostCode
只是发出一个请求,而另一个函数决定响应是否指示有效的代码。很好。
您(尽可能多地)将I / O与业务逻辑分开。具体来说,验证邮政编码的代码部分不会尝试读入或写出结果;它只是说:“这是否有效?”这意味着,如果您以后需要交换调用的API,或者可以对不需要进入外界的邮政编码进行一些内部检查,则可以稍后轻松地将其交换出去。通常最好的做法是在“层”中编写代码,将I / O作为代码的“外部”层,仅在I / O层“内部”进行验证,然后在验证层内部进行业务逻辑处理,以便业务逻辑代码可以信任它仅接收到有效数据。在这个简单的示例中,您没有任何业务逻辑,但是您将I / O和验证层正确地分开了。干得好。
答案 1 :(得分:2)
我不认为这里的目的应该是使代码“更具功能性”。发挥功能不是内在的价值。在F#中,保持逻辑核心正常运行是有意义的,但是如果您要执行大量I / O,那么遵循更强制性的样式是有意义的。
我的代码版本如下(与@rmunn的一些建议有些重叠):
let postCodeFile = "c:\\tmp\\randomPostCodes.txt"
let postCodeValidDaterUrl = "https://api.postcodes.io/postcodes/"
let lines = System.IO.File.ReadLines(postCodeFile)
let validatePostCode postCode =
Request.createUrl Get (postCodeValidDaterUrl + postCode)
|> getResponse
|> run
let translateResponse response =
response.statusCode = 200
for line in lines do
let valid = translateResponse (validatePostCode line)
printfn "%s-%b" line valid
我的更改是:
我删除了readLines
助手,而直接调用了File.ReadLines
。如果.NET方法没有达到更大的目的,例如提供在多个地方重用的F#友好API,则无需为.NET方法引入F#别名。
就像@rmunn一样,我将match
替换为response.statusCode = 200
。我仅在需要绑定新变量作为匹配的一部分时才使用match
。在测试布尔条件时,我认为if
更好。
我用普通的Seq.iter
循环替换了您的组合函数和for
。无论如何,代码都是必须的,因此我不明白为什么您不希望使用内置的语言构造。我删除了validPostCode
,因为您只在一个地方使用了组合函数,因此引入它并不会简化代码。
答案 2 :(得分:1)
这只是我的风格,不一定功能或更强:
open System.IO
let postCodeFile = "c:\\tmp\\randomPostCodes.txt"
let postCodeValidDaterUrl = "https://api.postcodes.io/postcodes/"
let validatePostCode postCode =
Request.createUrl Get (postCodeValidDaterUrl + postCode)
|> getResponse
|> run
|> fun response -> response.statusCode = 200
File.ReadLines postCodeFile
|> Seq.iter (fun code -> validatePostCode code |> printfn "%s-%b" code )