该表达式应具有类型“ unit”,但此处具有类型“ string”

时间:2018-08-09 06:38:18

标签: f# c#-to-f#

我试图将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
"?"

2 个答案:

答案 0 :(得分:8)

F#中的for循环用于运行命令式代码,其中for循环内的代码不会产生结果,而是会产生某种副作用。因此,预期F#for循环中的expression块会产生类型unit,这是副作用函数应返回的类型。 (例如,printfn "Something"返回unit类型)。而且,无法在F#的早期退出for循环。这是设计使然,也是for循环并不是执行您要执行的操作的最佳方法的另一个原因。

您要执行的操作是一次列出一个项目,找到符合某些条件的 first 项目,然后返回该项目(如果未找到该项目,则返回,返回一些默认值)。 F#具有专门的功能:Seq.find(如果List.find是F#列表,则是host.AddressList;如果Array.find是数组,则是host.AddressList。这三个函数采用不同的输入类型,但在概念上所有输入方式都相同,因此从现在开始,我将重点介绍Seq.find,它将任何IEnumerable作为输入,因此很可能是您在这里所需要的。) / p>

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

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

这意味着该函数采用两个参数'T -> boolseq<'T>并返回'T'T语法的意思是“这是一个称为T的通用类型”:在F#中,撇号表示其后是通用类型的名称。类型'T -> bool表示接受'T并返回布尔值的函数;即谓词为“是的,这符合我要查找的内容”或“否,请继续查找”。 Seq.find的第二个参数是seq<'T>seq是F#的IEnumerable的缩写,因此您可以将其读为IEnumerable<'T>。结果是'T类型的项目。

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

但是等等!如果您要查找的商品根本不在顺序中怎么办?然后Seq.find将引发异常,这可能不是您要查找的行为。这就是Seq.tryFind函数存在的原因:它的函数签名看起来像Seq.find,除了返回值:它返回'T option而不是'T。这意味着您将得到Some "ip address"None之类的结果。对于您自己的情况,如果找不到该项目,则打算返回"?"。因此,您想将Some "ip addressNone的值转换为"ip address"(无Some)或"?"的值。这就是defaultArg函数的作用:如果您的值是'T option,则返回一个'T和一个None代表默认值,然后返回一个纯文本'T

总而言之:

  • Seq.tryFind使用谓词函数和序列,并返回'T option。您的情况将是string option
  • defaultArg接受一个'T option和一个默认值,并返回一个普通的'T(在您的情况下为string)。

有了这两部分,再加上您可以编写自己的谓词功能,我们就可以满足您的需求。

在我向您展示代码之前,请多加注意:您写了let getIP : string = (code)。您似乎希望getIP成为一个函数,但没有给它任何参数。通过立即(仅一次)运行代码块并将其结果分配给名称let something = (code block),编写something将创建一个 value 。而编写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#可能无法推断ipisInternet自变量的类型这一事实。 。从代码中可以明显看出,它必须是具有.AddressFamily属性的 some 类,但是F#编译器无法(在代码中)知道哪个类。这是因为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是您要查找的方式,因此感觉更自然:用英语,您会说“请在我的壁橱中寻找“绿色衬衫”,因此“壁橱”的概念出现在“绿色衬衫”之前。在惯用的F#中,您会写成closet |> Seq.find (fun shirt -> shirt.Color = "green"):同样,“壁橱”的概念出现在“绿色衬衫”之前。

使用此版本的函数,F#在遇到host.AddressList之前会遇到fun ip -> ...,因此它将知道名称ip指的是host.AddressList中的一项。而且,由于它知道host.AddressList的类型,因此可以推断ip的类型。

*使用|>运算符在幕后还有很多事情,涉及curryingpartial application。但是在初学者级别,只需将其视为“在函数的参数列表的末尾输入一个值”,便会拥有正确的想法。

答案 1 :(得分:3)

在F#中,所有if / else / then语句的所有分支的求值类型必须相同。由于您省略了表达式的else分支,因此编译器将推断它返回类型为unit的值,从而有效地将if-expression转换为以下形式:

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()来解决此问题,或者可以使用类似{{ 3}}。