Search code examples
f#excel-dna

ExcelDna F# and optional arguments


For scalar (i.e. non array-like) optional arguments, I would use this pattern :

[<ExcelFunction(Category= "Test", Description= "Test optional arguments.")>]
let test_test1 ([<ExcelArgument(Description= "Optional. This is a double. Default is 42.0.")>] arg1 : obj) : double = 
    match arg1 with
    | :? ExcelMissing -> 42.0  // the argument was missing
    | :? double as d  -> d     // the argument was a double
    | _               -> -1.0  // otherwise

I am not sure if this code is "idiomatic" within Excel-Dna / F# but it seems to "work".

However I am not sure how to proceed for optional array-like arguments. Eg :

[<ExcelFunction(Category= "Test", Description= "Test optional arguments.")>]
let test_test2 ([<ExcelArgument(Description= "Optional. This is a double. Default is [42, 42].")>] arg1 : obj[]) : double[] = 
    match arg1.[0] with
    | :? ExcelMissing -> [42.0; 42.0] |> List.toArray // the argument was missing OR it was an empty array
    | :? double as d  -> arg1 |> castToDouble         // the argument was an array and its first argument was a double
    | _               -> Array.empty                  // otherwise

The above seems to work for most cases but does not allow to handle the edge-cases properly : eg if arg1 is an empty array. (castToDouble being a custom obj[] -> double[] conversion function)

What would be the right / idiomatic way to handle optional double arrays in F# / Excel-Dna and how could I then rewrite test_test2?

=========== EDIT ===

Following Govert's advice, I tried the following :

[<ExcelFunction(Category= "Test", Description= "Test optional arguments.")>]
let test_test3 ([<ExcelArgument(Description= "Optional. This is a double. Default is [42, 42].")>] arg1 : obj) : double[] = 
    match arg1 with
    | :? (double[]) as ds  -> [1.0; 2.0] |> List.toArray   // the argument was a array of double elements
    | :? ExcelMissing      -> [42.0; 42.0] |> List.toArray // the argument was missing OR it was an empty array
    | _                    -> Array.empty                  // otherwise        

... but unfortunately I get a #NUM! output when I pass an array of doubles (or of anything else). It's only when I pass nothing that I correctly get the [42.0, 42.0] array.


Solution

  • This covers all the possibilities:

    
    [<ExcelFunction("describes the input argument")>]
    let describe(arg1 : obj) : string =
        match arg1 with
            | :? double as d        -> sprintf "Double: %f" d
            | :? string as s        -> "String: " + s
            | :? bool as b          -> sprintf "Boolean: %b" b
            | :? ExcelError as err  -> sprintf "ExcelError: %A" err
            | :? (obj[,]) as arr    -> sprintf "Array[%i, %i]" (Array2D.length1 arr) (Array2D.length2 arr)
            | :? ExcelEmpty         -> "<<Empty>>"
            | :? ExcelMissing       -> "<<Missing>>"
            | _                     -> "!? Unheard of ?!"