这个表达式应该有'unit'类型,但这里有'string'类型

par*_*tor 2 f# c#-to-f#

我试图转换对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
"?"
Run Code Online (Sandbox Code Playgroud)

rmu*_*unn 7

forF#中的循环用于运行命令式代码,其中for循环内的代码不会产生结果,而是运行某种副作用.因此,for期望F#循环中的表达式块产生类型unit,这是副作用函数应该返回的类型.(例如,printfn "Something"返回unit类型).而且,forF#早期无法退出循环; 这是设计上的,也是for循环不是做你想要做的事情的最佳方法的另一个原因.

你要做的是一次查看一个项目列表,找到匹配某个条件的第一个项目,然后返回该项目(如果找不到该项目,则返回一些默认值).F#有一个专门的功能:( Seq.find或者List.find如果host.AddressList是一个F#列表,或者Array.find如果host.AddressList是一个数组.这三个函数采用不同的输入类型,但在概念上都以相同的方式工作,所以从现在开始我会专注于Seq.find,这需要任何IEnumerable作为输入,所以最有可能是你需要的).

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

('T -> bool) -> seq<'T> -> 'T
Run Code Online (Sandbox Code Playgroud)

这意味着该函数采用两个参数,a 'T -> boolseq<'T>并返回a 'T.该'T语法的意思是"这是一个称为T泛型类型":在F#,撇号后面跟的是一个通用型的名称.类型'T -> bool表示一个函数,它接受一个'T并返回一个布尔值; 即,一个谓词"是,这匹配我正在寻找的"或"不,继续寻找".第二个参数Seq.find是a 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取a 'T option和默认值,并返回法线'T(在您的情况下,a 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 "?"
Run Code Online (Sandbox Code Playgroud)

我希望这很清楚; 如果您有任何不明白的地方,请告诉我,我会进一步解释.

编辑:上面有一个可能的缺陷:如果没有显式类型声明,F#可能无法推断出ip参数isInternet的类型.这是从它需要代码清晰一些类的.AddressFamily属性,但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 "?"
Run Code Online (Sandbox Code Playgroud)

无论如何,这实际上是更加惯用的F#.当您传递谓词函数Seq.tryFind或其他类似函数时,F#中最常见的样式是使用fun关键字将谓词声明为匿名函数; 这就像C#中的lambdas一样(在C#中谓词就是这样ip => ip.AddressFamily == AddressFamily.InterNetwork).而另一件常见的事情是将|>运算符与类似的东西一起使用Seq.tryFind以及其他使用谓词的东西.该|>运营商基本上是*采取之前的价值|>运营商,并将其作为了最后的操作后,这是该函数的参数.所以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部分应用.但是在初学者级别,只需将其视为"在函数参数列表的末尾放置一个值",您就会有正确的想法.