可以使其更具功能性吗?

时间:2019-02-22 15:26:45

标签: f#

只是从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) 

关于使其更具功能性的任何建议?

3 个答案:

答案 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 (...),因为这可以让您跳过为中间步骤命名的方法。

为什么这样好

您在这里做得很好的两件事:

  1. 您编写的每个函数仅做一件事情,然后将这些函数组合在一起以创建更大的代码“构建块”。例如,validatePostCode只是发出一个请求,而另一个函数决定响应是否指示有效的代码。很好。

  2. 您(尽可能多地)将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 )