You need to make them static members of an intermediate type:
open System
type T = T with
static member ($) (T, x) = if Double.IsNaN(x) then None else Some (decimal x)
static member ($) (T,(x, y)) =
match (x, y) with
| (x, y) when not(Double.IsNaN(x)) && not (Double.IsNaN(y)) -> Some (decimal x, decimal y)
| (_, _) -> None
let inline parse x = T $ x
// Usage
let a = parse (1. , 2.)
// val a : (decimal * decimal) option = Some (1M, 2M)
let b = parse 1.
// val b : decimal option = Some 1M
Also note that it's better to use a binary operator to send also the intermediate type. This way you delay overload resolution.
You can also use named functions but it's more verbose.
EDIT Regarding delaying overload resolution.
First of all, I said in order to write it as a named function you have to write the constraints by hand:
let inline parse (x: ^a) =
((^b or ^a) : (static member ($) : ^b -> ^a -> _) T,x)
Note that the other answer that was later added is exactly the same as this, the only difference is that it uses a name ToOption for the static member instead of an operator.
Ok, now let's try to get rid of T, we can do it to some extent:
type T = T with
static member ($) x = if Double.IsNaN(x) then None else Some (decimal x)
static member ($) ((x, y)) =
match (x, y) with
| (x, y) when not(Double.IsNaN(x)) && not (Double.IsNaN(y)) -> Some (decimal x, decimal y)
| (_, _) -> None
let inline parse (x: ^a) =
let call (_:'b) = ((^b or ^a) : (static member ($) : ^a -> _) x)
call T
Note that I had to create a way to unify ^b with T, that's why I added a call function.
Now this still works and it's a valid solution which cleans up the overload signature and add some more noise in the parse function, which in many scenarios is a good trade-off. But what if I remove completely the ^a parameter from the trait call?
let inline parse (x: ^a) =
let call (_:'b) = (^b : (static member ($) : ^a -> _) x)
call T
this fails with
~vs6086.fsx(11,27): error FS0043: A unique overload for method 'op_Dollar' could not be determined based on type information prior to this program point. A type annotation may be needed.
Known return type: 'b
Known type parameter: < ^a >
Candidates:
- static member T.( $ ) : (float * float) -> (decimal * decimal) option
- static member T.( $ ) : x:float -> decimal option
Why is that?
Because now the F# compiler knows at the trait-call site all the type variables involved.
Before this last change ^a was unknown and ^b was unified with T, but since the other one was unknown, overload resolution was delayed to each future individual call of the parse function, which is doable since it's declared inline.
By knowing all type parameters, it tries to apply standard .Net overloading resolution and fails because there are 2 candidates.
Hope this explanation makes sense.