3

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?

Community
  • 1
  • 1
JDB
  • 25,172
  • 5
  • 72
  • 123
  • What does `myFunc` actually do? – Ganesh Sittampalam Jan 29 '14 at 21:37
  • @GaneshSittampalam - It looks up some *very* expensive resources in an efficient way. It's better to perform the lookup all at once rather than one at a time. – JDB Jan 29 '14 at 21:42
  • If the length of the array is fixed why not use a tuple? – Daniel Jan 29 '14 at 21:42
  • @Daniel - sounds like it's akin to Async.Parallel - people calling that would have a similar problem to this. – Ganesh Sittampalam Jan 29 '14 at 21:43
  • @Daniel - in some cases there are 2 items, in some cases three. I am looking up resources by a "tag" or "id", but due to the limitations of the API I'm working with, I have to pull *all* of the resources then iterate through them one at a time and examine the id. I'd rather start with the full list of ids and then get all of the resources I'll need in a single lookup. In the end, though, I don't need the resources themselves - just attributes of those resources. That's what I'm returning. – JDB Jan 29 '14 at 21:45
  • @Daniel - I'm also trying to "blackbox" it a bit - in case I find a better approach for getting those attributes later, I don't want to have to rewrite the parts of my code that need these attributes. – JDB Jan 29 '14 at 21:47
  • 2
    The compiler is reminding you of something true about your code, namely, that arrays are variable length and the type system does not guarantee that the items you're attempting to read from the array are actually present (even though _you know_ how many are there, it _is_ possible to be wrong). – Daniel Jan 29 '14 at 21:48
  • @Daniel - Which makes total sense and I like that the compiler is checking up on me. The question, though, is "is there an approach which is safer and doesn't produce a warning?" If I have to take a different tack, then I will... just a bit dissapointing to get this far and hit a snag like that. – JDB Jan 29 '14 at 21:49
  • Yes, used a fixed length type like a tuple or union. Along the lines of what Ganesh suggested, you could define several functions/overloads that return tuples of different sizes. Each could call into a function that does the heavy lifting and returns an array. You could use pattern matching in the calling functions and at least the messiness would be contained. – Daniel Jan 29 '14 at 21:52
  • @Daniel Yep - I can define a fixed size for the common scenarios (1 .. 4) and then just resort to pattern matching for the less common scenarios (greater than 4) – JDB Jan 29 '14 at 21:53

2 Answers2

5

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)
...
Ganesh Sittampalam
  • 28,821
  • 4
  • 79
  • 98
  • Ah! I like the first option. Simple and easy to implement! This will work for my scenario. Hmm... might be able to use generics to make it more scalable too. Thanks. – JDB Jan 29 '14 at 21:51
0

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)
CH Ben
  • 1,583
  • 13
  • 23