此表达式的类型应为 '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 -> bool
和seq<'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# 可能无法推断 isInternet
中 ip
参数的类型这一事实没有明确的类型声明。从代码中可以清楚地看出,它需要 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.
我试图将 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 -> bool
和seq<'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
.
但是等等!如果您要查找的项目根本不在序列中怎么办?然后 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# 可能无法推断 isInternet
中 ip
参数的类型这一事实没有明确的类型声明。从代码中可以清楚地看出,它需要 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.