此表达式的类型应为 'unit' 但此处的类型为 'string'

This expression was expected to have type 'unit' but here has type 'string'

我试图将 this 转换为 F#,但我无法弄清楚我做错了什么,因为错误消息(在标题中)过于宽泛而无法搜索,所以我未找到任何解决方案。

代码如下:

let getIP : string =
    let host = Dns.GetHostEntry(Dns.GetHostName())
    for ip in host.AddressList do
        if ip.AddressFamily = AddressFamily.InterNetwork then
            ip.ToString() // big fat red underline here
"?"

F# 中的一个 for 循环适用于 运行 命令式代码,其中 for 循环内的代码不产生结果而是 运行这是某种副作用。因此,F# for 循环中的表达式块应生成 unit 类型,这正是副作用函数应该 return 的类型。 (例如,printfn "Something" return 是 unit 类型)。此外,在 F# 中无法提前退出 for 循环;这是设计使然,也是 for 循环不是执行您想要执行的操作的最佳方法的另一个原因。

您要做的是一次浏览一个列表,找到符合某些条件的 第一个 项,然后 return 该项(并且,如果找不到该项目,return 一些默认值)。 F# 有专门的函数:Seq.find(或 List.find 如果 host.AddressList 是 F# 列表,或 Array.find 如果 host.AddressList 是数组。这三个函数采用不同的输入类型,但在概念上都以相同的方式工作,所以从现在开始我将专注于 Seq.find,它将任何 IEnumerable 作为输入,因此很可能是您在这里需要的)。 =101=]

如果您在 F# 文档中查看 Seq.find 函数的类型签名,您会发现它是:

('T -> bool) -> seq<'T> -> 'T

这意味着该函数有两个参数,一个'T -> boolseq<'T>和return一个'T'T 语法表示 "this is a generic type called T":在 F# 中,撇号表示后面是泛型类型的名称。类型 'T -> bool 表示一个函数接受一个 'T 和 return 一个布尔值;即,表示 "Yes, this matches what I'm looking for" 或 "No, keep looking" 的谓词。 Seq.find 的第二个参数是 seq<'T>seq 是 F# 对 IEnumerable 的简称,因此您可以将其读作 IEnumerable<'T>。结果是 'T.

类型的项目

仅从该函数签名和名称,您就可以猜出它的作用:它遍历项目序列并为每个项目调用谓词;谓词 return 为真的第一项将作为 Seq.find.

的结果 returned

但是等等!如果您要查找的项目根本不在序列中怎么办?然后 Seq.find 将抛出异常,这可能不是您要查找的行为。这就是 Seq.tryFind function exists: its function signature looks just like Seq.find, except for the return value: it returns 'T option rather than 'T. That means that you'll either get a result like Some "ip address" or None. In your case, you intend to return "?" if the item isn't found. So you want to convert a value that's either Some "ip address or None to either "ip address" (without the Some) or "?". That is what the defaultArg 函数用于的原因:如果您的值为 None,它需要一个 'T option 和一个代表默认值 return 的 'T,并且它 return 是一个普通的 'T

所以总结一下:

  • Seq.tryFind接受一个谓词函数和一个序列,return是一个'T option。在您的情况下,这将是 string option
  • defaultArg 采用 'T option 和默认值,return 是正常的 'T(在您的情况下,string)。

有了这两部分,再加上一个你可以自己写的谓词函数,我们就可以做你想要的了。

在我给你看代码之前还有一点要注意:你写了 let getIP : string = (code)。看起来你打算让 getIP 成为一个函数,但你没有给它任何参数。编写 let something = (code block) 将创建一个 ,方法是 运行 立即(并且只执行一次)代码块,然后将其结果分配给名称 something。而写 let something() = (code block) 将创建一个 函数 。它不会立即 运行 代码块,而是会在每次调用函数时 运行 代码块。所以我认为你应该写 let getIP() : string = (code).

好吧,解释了所有这些,让我们把它们放在一起给你一个真正有效的 getIP 函数:

let getIP() =  // No need to declare the return type, since F# can infer it
    let isInternet ip =  // This is the predicate function
        // Note that this function isn't accessible outside of getIP()
        ip.AddressFamily = AddressFamily.InterNetwork
    let host = Dns.GetHostEntry(Dns.GetHostName())
    let maybeIP = Seq.tryFind isInternet host.AddressList
    defaultArg maybeIP "?"

我希望你说得够清楚;如果有什么不明白的地方,请告诉我,我会尽力解释。

编辑: 以上可能存在一个缺陷:F# 可能无法推断 isInternetip 参数的类型这一事实没有明确的类型声明。从代码中可以清楚地看出,它需要 some class with an .AddressFamily 属性,但是 F# 编译器无法知道(此时point in the code) which class 你打算传递给这个谓词函数。这是因为 F# 编译器是一个 单通道 编译器,它以自上而下、从左到右的顺序处理代码。为了能够推断出 ip 参数的类型,您可能需要稍微重新安排代码,如下所示:

let getIP() =  // No need to declare the return type, since F# can infer it
    let host = Dns.GetHostEntry(Dns.GetHostName())
    let maybeIP = host.AddressList |> Seq.tryFind (fun ip -> ip.AddressFamily = AddressFamily.InterNetwork)
    defaultArg maybeIP "?"

无论如何,这实际上更符合 F# 的习惯。当您将谓词函数传递给 Seq.tryFind 或其他类似函数时,F# 中最常见的样式是使用 fun 关键字将该谓词声明为匿名函数;这就像 C# 中的 lambda 一样工作(在 C# 中谓词将是 ip => ip.AddressFamily == AddressFamily.InterNetwork)。另一件常见的事情是将 |> 运算符与 Seq.tryFind 和其他采用谓词的东西一起使用。 |> 运算符基本上* 获取 |> 运算符之前的值,并将其作为运算符之后的函数的 last 参数传递。所以 foo |> Seq.tryFind (fun x -> xyz) 就像写 Seq.tryFind (fun x -> xyz) foo 一样,除了 foo 是您在该行中读到的第一个内容。由于 foo 是您正在查找的序列,而 fun x -> xyz 是您正在查找的 如何,所以感觉更自然:在英语中,您d 说 "Please look in my closet for a green shirt",所以概念 "closet" 在 "green shirt" 之前出现。在惯用的 F# 中,您将编写 closet |> Seq.find (fun shirt -> shirt.Color = "green"):同样,概念 "closet" 在 "green shirt".

之前出现

使用此版本的函数,F# 将在遇到 fun ip -> ... 之前遇到 host.AddressList,因此它会知道名称 ip 指的是 [=23= 中的一项].因为它知道 host.AddressList 的类型,所以它能够推断出 ip.

的类型

* |> 运算符在幕后还有很多事情要做,涉及 currying and partial application。但对于初学者来说,只要把它想象成 "puts a value at the end of a function's parameter list",你就会有正确的想法。

在 F# 中,任何 if/else/then-statement 的所有分支都必须计算出相同类型的值。由于您省略了表达式的 else 分支,编译器会将其推断为 return unit 类型的值,有效地将您的 if 表达式转换为:

if ip.AddressFamily = AddressFamily.InterNetwork then
    ip.ToString() // value of type string
else
    () // value of type unit

Scott Wlaschin 在出色的 F# for fun and profit 上比我解释得更好。

这应该可以修复当前的错误,但仍然无法编译。您可以通过更直接地翻译 C# 代码(使用可变变量作为 localIP 值,并在您的 if 子句中执行 localIP <- ip.ToString() 来解决此问题,或者您可以使用类似 Seq.tryFind.