Alternative approach to avoid "Incomplete pattern match" warning

1.3k views Asked by At

I have written a function that takes an array as input and returns an array of equal size as output. For example:

myFunc [| "apple"; "orange"; "banana" |]
> val it : (string * string) [] =
    [|("red", "sphere"); ("orange", "sphere"); ("yellow", "oblong")|]

Now I want to assign the results via a let binding. For example:

let [|
        ( appleColor, appleShape );
        ( orangeColor, orangeShape );
        ( bananaColor, bananaShape )
    |] = 
    myFunc [| "apple"; "orange"; "banana" |]

Which works great...

> val orangeShape : string = "sphere"
> val orangeColor : string = "orange"
> val bananaShape : string = "oblong"
> val bananaColor : string = "yellow"
> val appleShape : string = "sphere"
> val appleColor : string = "red"

...except it produces a warning:

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

The source and reason for the warning has already been covered, I'm just looking for a succinct work-around. This function call occurs near the top of my function, and I don't like the idea of putting the entire function body inside a match:

let otherFunc =
    match myFunc [| "apple"; "orange"; "banana" |] with
    | [|
        ( appleColor, appleShape );
        ( orangeColor, orangeShape );
        ( bananaColor, bananaShape )
      |] ->
        // ... the rest of my function logic
    | _ -> failwith "Something impossible just happened!"

That just smells bad. I don't like the idea of ignoring the warning either - goes against my better judgment. Are there any other options open to me, or do I just need to find a different approach entirely?

2

There are 2 answers

1
Ganesh Sittampalam On BEST ANSWER

One possibility if you expect this kind of calling pattern to be frequent is to make wrappers that act on the sizes of tuples you expect, e.g.

myFunc3 (in1,in2,in3) =
    match myFunc [|in1;in2;in3|] with
    [|out1;out2;out3|] -> out1, out2, out3
    _ -> failwith "Internal error"

etc. But all it does is move the ugly code to a standard place, and writing out the wrappers will be inconvenient.

I don't think there's any better option with this API, because there's no way to tell the compiler that myFunc always returns the same number of elements it is passed.

Another option might be to replace myFunc with an IDisposable class:

type MyClass() =
   let expensiveResource = ...


   member this.MyFunc(v) = ...calculate something with v using expensiveResource

   interface IDisposable with
       override this.Dispose() = // cleanup resource

and then use it in a block like

use myClass = new MyClass()
let appleColor, appleShape = myClass.MyFunc(apple)
...
0
CH Ben On

Adapting @Ganesh's answer, here's a primitive way to approach the problem:

let Tuple2Map f (u, v) 
    = (f u, f v)

let Tuple3Map f (u, v, w) 
    = (f u, f v, f w)

let Tuple4Map f (u, v, w, x) 
    = (f u, f v, f w, f x)

Example:

let Square x = x * x
let (a,b) = Tuple2Map Square (4,6)
// Output:
// val b : int = 36 
// val a : int = 16

But I guess something even more primitive would be this:

let Square x = x * x
let (a,b) = (Square 4, Square 6)

And if the function name is too long, e.g.

// Really wordy way to assign to (a,b)
let FunctionWithLotsOfInput w x y z = w * x * y * z
let (a,b) = 
    (FunctionWithLotsOfInput input1 input2 input3 input4A,
     FunctionWithLotsOfInput input1 input2 input3 input4B)

We can define temporary function

let FunctionWithLotsOfInput w x y z = w * x * y * z
// Partially applied function, temporary function 
let (a,b) =
    let f = (FunctionWithLotsOfInput input1 input2 input3)
    (f input4A, f input4B)