Destructuring a list in a let complains about an incomplete pattern match

I have a function taking a list as argument and simply mapping this list, so returning a list of the same length. So I hoped to use pattern matching in the assignment like this:

let [s1;s2] = getServices [typeof<web.I18n>;typeof<DB.DBClient>]

But this gets a warning:

Incomplete pattern matches on this expression. For example, the value '[_;_;_]' may indicate a case not covered by the pattern(s)

I understand the warning, but wondered if there was a way to achieve what I want without a warning.

I’m going assume getServices looks something like

let getServices xs = xs |> List.map getService

As far as the type signature of getServices, it takes a list and returns a list, and the types don’t indicate in any way that the lists have to be the same length. There isn’t really a way in F# to say that a list has a known length. So you’ve got some options: you could just do it the manual/boring way:

let s1 = getService typeof<web.I18n>
let s2 = getService typeof<DB.DBClient>
// or
let s1, s2 = getService typeof<web.I18n>, getService typeof<DB.DBClient>

You could just “bypass” the warning by not using pattern matching

let s1, s2 = 
  let services = getServices [typeof<web.I18n>; typeof<DB.DBClient>]
  in services[0], services[1]

If you don’t mind adding some extra helper code, oftentimes when you find yourself wanting to have a list of a fixed length, tuples are the right tool to reach for. Unfortunately, some of the functionality around tuples isn’t built-up the same as for lists. In my company’s codebase, we have some extra helper modules for tuples, e.g.,

module Tuple2 = 
  let map fn (x1, x2) = (fn x1, fn x2)

which gets a fair bit of use. Here, you could do

let (s1, s2) = Tuple2.map getService (typeof<web.I18n>, typeof<DB.DBClient>)
1 Like

Thanks for your answer!

Here is the definition of getServices:

    let getServices (services: System.Type list) =
        let ctx:WebSharper.Web.Context = WebSharper.Web.Remoting.GetContext()
        let httpContext:Microsoft.AspNetCore.Http.HttpContext = unbox (ctx.Environment.["WebSharper.AspNetCore.HttpContext"])
        let serviceProvider= httpContext.RequestServices
        services
        |> List.map (fun t -> unbox (serviceProvider.GetService(typeof<'T>)))

I wanted to avoid calling getService multiple times as the first assignments would be repeated (or they would have to be passed as argument, but at that time the added value of the helper is diminished).

I hadn’t thought of the “bypass”, good idea!