gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 13:12:38 25 4
gpt4 key购买 nike

我试图将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循环中的expression块将产生unit类型,这是副作用函数应返回的类型。 (例如printfn "Something"返回unit类型)。另外,无法在F#的早期退出for循环。这是设计使然,也是for循环并不是执行您要执行的操作的最佳方法的另一个原因。

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

如果您在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来创建一个值。而编写 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属性的某个类,但是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#中的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 -> ...,因此它将知道名称 iphost.AddressList中的一项。并且由于它知道 host.AddressList的类型,因此可以推断 ip的类型。

    * |>运算符在幕后进行了很多操作,涉及 curryingpartial application。但是,对于初学者而言,只需将其视为“在函数的参数列表的末尾输入一个值”,便会拥有正确的想法。

    关于f# - 该表达式应具有类型“unit”,但此处具有类型“string”,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51760327/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com