【问题标题】:Match with empty sequence与空序列匹配
【发布时间】:2016-12-18 19:59:49
【问题描述】:

我正在学习 F#,我已经开始使用序列和 match 表达式。

我正在编写一个网络爬虫,它通过类似于以下内容的 HTML 进行查看,并使用 paging 类获取父级 <span> 中的最后一个 URL。

<html>
<body>
    <span class="paging">
        <a href="http://google.com">Link to Google</a>
        <a href="http://TheLinkIWant.com">The Link I want</a>
    </span>
</body>
</html>

我尝试获取最后一个网址如下:

type AnHtmlPage = FSharp.Data.HtmlProvider<"http://somesite.com">

let findMaxPageNumber (page:AnHtmlPage)= 
    page.Html.Descendants()
    |> Seq.filter(fun n -> n.HasClass("paging"))
    |> Seq.collect(fun n -> n.Descendants() |> Seq.filter(fun m -> m.HasName("a")))
    |> Seq.last
    |> fun n -> n.AttributeValue("href")

但是,当我正在搜索的课程不在页面上时,我遇到了问题。特别是我收到带有以下消息的 ArgumentExceptions:Additional information: The input sequence was empty.

我的第一个想法是构建另一个匹配空序列并在页面上找不到 paging 类时返回空字符串的函数。

let findUrlOrReturnEmptyString (span:seq<HtmlNode>) =
    match span with 
    | Seq.empty -> String.Empty      // <----- This is invalid
    | span -> span
    |> Seq.collect(fun (n:HtmlNode) -> n.Descendants() |> Seq.filter(fun m -> m.HasName("a")))
    |> Seq.last
    |> fun n -> n.AttributeValue("href")

let findMaxPageNumber (page:AnHtmlPage)= 
    page.Html.Descendants()
    |> Seq.filter(fun n -> n.HasClass("paging"))
    |> findUrlOrReturnEmptyStrin

我现在的问题是 Seq.Empty 不是文字,不能在模式中使用。大多数具有模式匹配的示例在其模式中指定空列表[],所以我想知道:如何使用类似的方法来匹配空序列?

【问题讨论】:

  • 在这里使用if .. elsematch 只是让事情复杂化。 (if Seq.isEmpty span then "" else ...)
  • 这个例子已经被简化了,在我的管道中有几个地方我必须开始添加if-else。由于我是 F# 新手,我主要想知道是否有合适的方法来匹配空序列,因为匹配空序列似乎很常见。
  • 如果它在 你的 代码中很常见,并且你坚持使用match,那么为它创建一个活动模式。

标签: f# pattern-matching seq guard-clause


【解决方案1】:

您可以使用when 保护来进一步限定案例:

match span with 
| sequence when Seq.isEmpty sequence -> String.Empty
| span -> span
|> Seq.collect (fun (n: HtmlNode) ->
    n.Descendants()
    |> Seq.filter (fun m -> m.HasName("a")))
|> Seq.last
|> fun n -> n.AttributeValue("href")

ildjarn 是正确的,在这种情况下,if...then...else 可能是更易读的替代方案。

【讨论】:

  • 这不应该编译。 spanseq&lt;HtmlNode&gt; 类型,如果定义了 String.Empty,据我所知,它肯定应该是字符串类型。您可能是要缩进管道吗?否则,使用像if Seq.isEmpty span then "" else 这样的单行提早方式来允许打开分支而不需要更深的缩进。
  • 没错;它不编译。我知道如果不设置包括 HTML 类型提供程序在内的整个内容,它无论如何都不会编译,所以我没有努力修复它。至少编译匹配表达式会更好,是的。
  • String.Empty 从 .NET 1.0 开始定义。当然前提是你open System
【解决方案2】:

使用保护子句

match myseq with
| s when Seq.isEmpty s -> "empty"
| _ -> "not empty"

【讨论】:

    【解决方案3】:

    基于@rmunn 的答案,您可以制作更通用的序列相等活动模式。

    let (|Seq|_|) test input =
        if Seq.compareWith Operators.compare input test = 0
            then Some ()
            else None
    
    match [] with
    | Seq [] -> "empty"
    | _ -> "not empty"
    

    【讨论】:

    • 顺便说一句,您可能认为将活动模式命名为Seq 会与Seq 模块冲突,但它不会。您仍然可以使用Seq.appendSeq 模块中的其他功能;编译器会弄清楚的。在匹配模式中,名称Seq 将引用活动模式;在模式之外,名称Seq 将继续引用该模块。
    【解决方案4】:

    ildjarn 在 cmets 中给出的建议是一个很好的建议:如果您觉得使用 match 会创建更具可读性的代码,那么创建一个活动模式来检查空 seq:

    let (|EmptySeq|_|) a = if Seq.isEmpty a then Some () else None
    
    let s0 = Seq.empty<int>
    
    match s0 with
    | EmptySeq -> "empty"
    | _ -> "not empty"
    

    在 F# 交互中运行它,结果将是 "empty"

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-12
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多