Cogito Ergo Sum
Cogito Ergo Sum

Reputation: 810

F#: Can the match expression not return sequences when mapped to from non sequence type?

I am trying to create a recursive function that is conditionally calls itself and so far is is defined as follows:

let rec crawlPage (page : String, nestingLevel : int) =    
    HtmlDocument.Load(page)
    |> fun m -> m.CssSelect("a")
    |> List.map(fun a -> a.AttributeValue("href"))
    |> Seq.distinctBy id
    |> Seq.map (fun x -> baseUrl + x)
    |> Seq.map (fun x -> 
        match nestingLevel with
        // Compiler says it is expecting a but given seq<a> in reference to the recursive call
        | _ when (nestingLevel > 0) -> crawlPage(x, (nestingLevel - 1)) 
        | _ when (nestingLevel <= 0) -> ignore
        | _ -> (* To silence warnigs.*) ignore
    )

It is that the Seq.map (fun x -> ...) cannot handle the return sequence or can the match condition not handle the returned sequence? Given that the crawlPage is underlined by the compiler it seems that the match statement cannot handle the seq returned so how can this be done?

Upvotes: 1

Views: 69

Answers (2)

Tomas Petricek
Tomas Petricek

Reputation: 243041

The existing post answers your specific question, but I think it is worth noting that there are a few other changes that could be done to your code snippet. Some of those are a matter of personal preference, but I believe they make your code simpler:

  • You can use sequence comprehension, which lets you handle recursive calls nicely using yield! (and non-recursive using yield)
  • You do not actually need match, because you have just two branches that are more easily tested using ordinary if
  • I would also avoid the |> fun m -> m.Xyz pattern, because it's not necessary here.

With all those tweaks, my preferred version of the code snippet would be:

let rec crawlPage (page : String, nestingLevel : int) = seq {
    let urls = 
        HtmlDocument.Load(page).CssSelect("a")
        |> List.map(fun a -> a.AttributeValue("href"))
        |> Seq.distinctBy id
        |> Seq.map (fun x -> baseUrl + x)

    for x in urls do
        if nestingLevel > 0 then
            yield! crawlPage(x, (nestingLevel - 1)) 
        else 
            yield x }

Upvotes: 0

Nghia Bui
Nghia Bui

Reputation: 3784

The rule is that all the matching branches must return the same type, so you have to:

  1. Replace ignore with Seq.singleton x to indicate that this branch yields nothing more except the x itself.

  2. At the end, concat (flat map) the seq<seq<string>> to transform it to a seq<string>.

The code would be:

|> Seq.map (fun x -> 
    match nestingLevel with
    | _ when (nestingLevel > 0) -> crawlPage(x, (nestingLevel - 1)) 
    | _ -> Seq.singleton x)
|> Seq.concat

Upvotes: 1

Related Questions