我试图将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
"?"
答案 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 -> bool
和seq<'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 address
或None
的值转换为"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#可能无法推断ip
中isInternet
自变量的类型这一事实。 。从代码中可以明显看出,它必须是具有.AddressFamily
属性的 some 类,但是F#编译器无法(在代码中)知道哪个您打算传递给此谓词函数的em>类。这是因为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
的类型。
*使用|>
运算符在幕后还有很多事情,涉及currying和partial 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}}。