F# 这能变得更实用吗?

F# 这能变得更实用吗?,f#,F#,从f#开始,我来到OO C#背景,我有以下代码读取英国postscodes的文本文件,然后用post代码到达api端点,我测试结果以查看post是否有效: let postCodeFile = "c:\\tmp\\randomPostCodes.txt" let readLines filePath = System.IO.File.ReadLines(filePath) let lines = readLines postCodeFile let postCodeValidDaterUr

从f#开始,我来到OO C#背景,我有以下代码读取英国postscodes的文本文件,然后用post代码到达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) 
有什么建议让它更实用吗

评论/代码审查 你已经让它尽可能地发挥作用了。我将仔细阅读您的代码,并解释为什么您的决定是好的,在一些情况下,您可以做一些小的改进

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
我可能会停止创建
名称,而改为创建
postCodeFile |>readLines |>Seq.iter(…)
在代码的最后一行。您的操作方式没有本质上的错误,但是您没有在其他任何地方使用
变量,因此没有真正的理由给它命名。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)
。但是
match
表达式允许您在以后扩展它,如果您有一个可以返回其他状态代码(如204)的API,以指示成功。我可能会在这里使用更简单的比较,并仅在需要时添加
match
语句

let validPostCode = validatePostCode >> translateResponse
很好

正如我前面提到的,
是一个中间步骤,因此我可能会将其更改为
postCodeFile |>readLines |>Seq.iter(…)
,因为这允许您跳过为中间步骤命名

let postCodeValidDaterUrl = "https://api.postcodes.io/postcodes/"
为什么这是好的 有两件事你在这里做得很好:

  • 您编写每个函数只是为了做一件事,并将函数组合在一起以创建更大的代码“构建块”。例如,
    validatePostCode
    只发送一个请求,另一个函数决定响应是否指示有效代码。这很好

  • 您将I/O与业务逻辑分离(尽可能地分离)。具体而言,验证post代码的代码部分不会尝试读入或写出结果;它只会说“这是否有效?”这意味着,如果您以后需要交换您调用的API,或者如果您可以对不需要进入外部世界的post代码进行一些内部检查,那么您可以在以后轻松地交换。通常,在“层”中编写代码是很好的做法,I/O是代码的“外部”层,验证只是“内部”I/O层,然后是验证层内的业务逻辑-这样您的业务逻辑代码就可以相信它只收到了有效数据。在这个简单的示例中,您没有任何业务逻辑,但是I/O层和验证层已经正确地分开了。做得好


  • 我认为这里的目标不应该是使代码“更具功能性”。功能性不是一种固有的价值。在F#中,保持逻辑核心的功能性是有意义的,但如果你做了大量的I/O,那么遵循更命令式的风格是有意义的

    我的代码版本如下所示(与@rmunn的一些建议有些重叠):

    我的改变是:

    • 我删除了
      readLines
      帮助程序,只需直接调用
      File.readLines
      。如果.NET方法没有更好的用途,比如提供一个在多个地方重用的更为F友好的API,那么就不需要为它引入F别名

    • 像@rmunn一样,我用
      response.statusCode=200
      替换了
      match
      。我只在需要绑定新变量作为匹配的一部分时使用
      match
      。在测试布尔条件时,我认为
      if
      更好

    • 我用一个普通的
      for
      循环替换了你的组合函数和
      Seq.iter
      。无论如何,代码是必需的,所以我不明白你为什么不想使用内置语言构造。我取消了
      validPostCode
      ,因为你只在一个地方使用组合函数,所以引入它不会简化代码


      • 这只是我的风格,不一定更实用或更好:

        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 )
        

        副作用进来,副作用出去。真的有什么好的理由让它更“实用”吗考虑到它本质上是程序性的?啊,这是一个很好的观点,正如我刚才说的,刚刚开始并在那里试验一些有用的外卖。非常感谢,我目前正在使用f#进行编程,并使域建模功能化。需要开始做一些更复杂的事情。
        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 )