I'm trying to lookup DbType enumeration values from .net types. I'm using a match statement. However I cannot figure out how to match on the type byte[].
let dbType x =
match x with
| :? Int64 -> DbType.Int64
| :? Byte[] -> DbType.Binary // this gives an error
| _ -> DbType.Object
If there is a better way to map these types, I would be open to suggestions.
byte[], byte array, and array<byte> are all synonymous, but in this context only the last will work without parentheses:
let dbType (x:obj) =
match x with
| :? (byte[]) -> DbType.Binary
| :? (byte array) -> DbType.Binary // equivalent to above
| :? array<byte> -> DbType.Binary // equivalent to above
| :? int64 -> DbType.Int64
| _ -> DbType.Object
Related
I have built a toy spreadsheet to help learn F#. When I process the text for a new cell I store it as a discriminated type. To parse it I feel I should be able to do something like:
let cv =
match t with
| _ when t.Length=0 -> Empty
| x when t.[0]='=' -> Expr(x)
| x when t.[0]='\"' -> Str(x)
| (true,i) when Int32.TryParse t -> IntValue(i) // nope!
| _ -> Str(t)
I have tried quite a few combinations but I cannot get TryParse in the guard. I have written a helper:
let isInt (s:string) =
let mutable m:Int64 = 0L
let (b,m) = Int64.TryParse s
b
I can now write:
| _ when Utils.isInt t -> IntValue((int)t)
This seems like a poor solution as it discards the converted result. What the correct syntax to get TryParse into the guard?
I think an active pattern will do what you want:
let (|Integer|_|) (str: string) =
let flag, i = Int32.TryParse(str)
if flag then Some i
else None
let cv =
match t with
| _ when t.Length=0 -> Empty
| x when t.[0]='=' -> Expr(x)
| x when t.[0]='\"' -> Str(x)
| Integer i -> IntValue(i)
| _ -> Str(t)
But if you really want TryParse in the guard condition (and you don't mind parsing twice), you could do this:
| x when fst (Int32.TryParse(t)) -> IntValue (Int32.Parse(x))
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.
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 ?!"
Given a DU like
type Result<'a, 'b> = Ok of 'a | Error of 'b
and some functions
let doA () = Ok true
let doB () = Error <| exn "Fail"
let doC = function | 1 -> Ok "one" | x -> Error x
How do you define a function to cast the value?
toObjResult : x:obj -> Result<obj, obj> //where x is guaranteed to be Result<'a,'b>
Usage
let data =
[ doA() |> box
doB() |> box
docC 1 |> box
docC 2 |> box ]
|> List.map toObjResult
All attempts so far restrict the types of 'a and 'b to be obj
let toObjResult (x:obj) =
match x with
| :? Result<'a, 'b> as r ->
match r with
| Ok a -> Ok (box a)
| Error b -> Error (box b)
| _ -> Error <| (exn "Invalid type" |> box)
resulting in errors like
System.InvalidCastException: Unable to cast object of type 'Ok[System.Boolean,System.Object]' to type 'Result`2[System.Object,System.Object]'.
There is no way to do this without using reflection, enumerating all types, or modifying the type.
Using reflection can be slow, but lets you do what you want (see [the GenericType active pattern from this answer) and the answer from #robkuz shows how you can do this by listing all the cases that you want to cover - the problem is that this does not scale well.
Finally, if you were happy to modify your Result<'a, 'b> type, you could add a non-generic interface that lets you get the value as a boxed value:
type IBoxedResult =
abstract Boxed : Result<obj, obj>
and Result<'a, 'b> =
| Ok of 'a
| Error of 'b
interface IBoxedResult with
member x.Boxed =
match x with
| Ok v -> Ok (box v)
| Error v -> Error (box v)
Now you can cast obj to IBoxedResult and use Boxed to get the value as Reslt<obj, obj>:
[ box (Ok true)
box (Ok 1) ]
|> List.map (fun o -> (o :?> IBoxedResult).Boxed)
You have to match on the exact generic type params of your Result type in your matching expression
let matchR r =
match r with
| Ok a -> Ok (box a)
| Error b -> Error (box b)
let toObjResult (x:obj) =
match x with
| :? Result<bool, _> as r -> matchR r
| :? Result<string, int> as r -> matchR r
| :? Result<_, Exception> as r -> matchR r
| _ -> Error (box "Invalid type" )
sadly you can't match on unrealised type params (which is really bad)
How can I pattern match on a boxed tuple? Or is there a better way to do something like this (simplified example):
open System.Drawing
let coerceColor a =
match box a with
| :? Color as c -> c
| (:? int as r),(:? int as g),(:? int as b) -> Color.FromArgb(r,g,b)
| _ -> failwith "Cannot coerce color"
let coerceColor a =
match box a with
| :? Color as c -> c
| :? (int*int*int) as t -> t |> Color.FromArgb
| _ -> failwith "Cannot coerce color"
But if I could change the design, I would rather use a DU or alternatively a static member with overloads.
I'm trying to create a print statement that can print any type. I want to use type pattern matching to achieve this.
This doesn't work:
let print x = match x with | :? int -> printf "INT"; | _ -> None;;
I get the message:
let print x = match x with | :? int -> printf "INT"; | _ -> None;;
-----------------------------^^^^^^
stdin(47,30): error FS0008: This runtime coercion or type test from
type
'a to
int involves an indeterminate type based on information prior to this program point. Runtime type tests are not allowed on some types.
Further type annotations are needed.
So, i can't do type matching on an int? What other types can i not do type matching on? What does it mean that further type annotations are needed?
Type test is performed on reference types only. Therefore:
let print x =
match box x with
| :? int -> printf "INT"
| _ -> ()
or
let print (x: obj) =
match x with
| :? int -> printf "INT"
| _ -> ()
would work.
Notice that your function doesn't type check since None is of option type which is different from unit type of printf "INT".