I am writing some code to process a large data file. The CSV TypeProvider will return NaN for a given value if the field is none. I have quite a few fields, so it's cleaner if I write a helper function to check for that NaN and return a None instead. Not knowing how to write a more generic helper function, I came up with this:
let (!?) x = if Double.IsNaN(x) then None else Some (decimal x)
let (!?) (x, y) =
match (x, y) with
| (x, y) when not(Double.IsNaN(x)) && not (Double.IsNaN(y)) -> Some (decimal x, decimal y)
| (_, _) -> None
Unfortunately, my attempt to operator overload is not working correctly and duplicating code isn't great either. A newbie asks, is there a better way to do this?
(I am aware of something like PreferOptionals, but I need to do this more selectively)
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.
An F# binding has a single definition. Defining it again simply shadows the previous definition.
let f x = x
let f x = x * x //previous f is now shadowed
Here's a way to use statically resolved types to achieve the same result. We're defining the members on an intermediate type (a single-case DU) called Converter
:
type Converter = Converter with
static member ToOption (_ : Converter, value) =
if Double.IsNaN(value) then None else Some(value)
static member ToOption (_ : Converter, (x, y)) =
if Double.IsNaN(x) || Double.IsNaN(y) then None else Some(x, y)
let inline (!?) (x : ^a) =
((^b or ^a) : (static member ToOption : ^b * ^a -> _) (Converter, x))
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.