Skip to content

Commit 3765b23

Browse files
authored
Adds Value/Option.ofNull and bindNull (#164)
1 parent ad50680 commit 3765b23

File tree

4 files changed

+152
-16
lines changed

4 files changed

+152
-16
lines changed

src/FsToolkit.ErrorHandling/Option.fs

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@ namespace FsToolkit.ErrorHandling
33
[<RequireQualifiedAccess>]
44
module Option =
55

6-
let ofValueOption (vopt: _ voption) =
6+
let inline ofValueOption (vopt: _ voption) =
77
match vopt with
88
| ValueSome v -> Some v
99
| ValueNone -> None
1010

11-
let toValueOption (opt: _ option) =
11+
let inline toValueOption (opt: _ option) =
1212
match opt with
1313
| Some v -> ValueSome v
1414
| None -> ValueNone
1515

16-
let traverseResult f opt =
16+
let inline traverseResult f opt =
1717
match opt with
1818
| None -> Ok None
1919
| Some v -> f v |> Result.map Some
2020

21-
let sequenceResult opt = traverseResult id opt
21+
let inline sequenceResult opt = traverseResult id opt
2222

2323
#if !FABLE_COMPILER
2424
let inline tryParse< ^T when ^T: (static member TryParse : string * byref< ^T > -> bool) and ^T: (new : unit -> ^T)>
@@ -50,13 +50,48 @@ module Option =
5050
/// <param name="option1">The input option</param>
5151
/// <param name="option2">The input option</param>
5252
/// <returns></returns>
53-
let zip (option1: 'a option) (option2: 'b option) =
53+
let inline zip (option1: 'a option) (option2: 'b option) =
5454
match option1, option2 with
5555
| Some v1, Some v2 -> Some(v1, v2)
5656
| _ -> None
5757

5858

59-
let ofResult =
60-
function
59+
let inline ofResult (r: Result<_, _>) =
60+
match r with
6161
| Ok v -> Some v
6262
| Error _ -> None
63+
64+
/// <summary>
65+
/// Convert a potentially null value to an option.
66+
///
67+
/// This is different from <see cref="FSharp.Core.Option.ofObj">Option.ofObj</see> where it doesn't require the value to be constrained to null.
68+
/// This is beneficial where third party APIs may generate a record type using reflection and it can be null.
69+
/// See <a href="https://latkin.org/blog/2015/05/18/null-checking-considerations-in-f-its-harder-than-you-think/">Null-checking considerations in F#</a> for more details.
70+
/// </summary>
71+
/// <param name="value">The potentially null value</param>
72+
/// <returns>An option</returns>
73+
/// <seealso cref="FSharp.Core.Option.ofObj"/>
74+
let inline ofNull (value: 'nullableValue) =
75+
if System.Object.ReferenceEquals(value, null) then
76+
None
77+
else
78+
Some value
79+
80+
81+
/// <summary>
82+
///
83+
/// <c>bindNull binder option</c> evaluates to <c>match option with None -> None | Some x -> binder x |> Option.ofNull</c>
84+
///
85+
/// Automatically onverts the result of binder that is pontentially null into an option.
86+
/// </summary>
87+
/// <param name="binder">A function that takes the value of type 'value from an option and transforms it into
88+
/// a value of type 'nullableValue.</param>
89+
/// <param name="option">The input option</param>
90+
/// <typeparam name="'value"></typeparam>
91+
/// <typeparam name="'nullableValue"></typeparam>
92+
/// <returns>An option of the output type of the binder.</returns>
93+
/// <seealso cref="ofNull"/>
94+
let inline bindNull (binder: 'value -> 'nullableValue) (option: Option<'value>) =
95+
match option with
96+
| Some x -> binder x |> ofNull
97+
| None -> None

src/FsToolkit.ErrorHandling/ValueOption.fs

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,22 @@ namespace FsToolkit.ErrorHandling
44
[<RequireQualifiedAccess>]
55
module ValueOption =
66

7-
let ofOption (opt: 'a option) =
7+
let inline ofOption (opt: 'a option) =
88
match opt with
99
| Some v -> ValueSome v
1010
| None -> ValueNone
1111

12-
let toOption (vopt: 'a voption) =
12+
let inline toOption (vopt: 'a voption) =
1313
match vopt with
1414
| ValueSome v -> Some v
1515
| ValueNone -> None
1616

17-
let traverseResult f vopt =
17+
let inline traverseResult f vopt =
1818
match vopt with
1919
| ValueNone -> Ok ValueNone
2020
| ValueSome v -> f v |> Result.map ValueSome
2121

22-
let sequenceResult opt = traverseResult id opt
22+
let inline sequenceResult opt = traverseResult id opt
2323

2424
let inline tryParse< ^T when ^T: (static member TryParse : string * byref< ^T > -> bool) and ^T: (new : unit -> ^T)>
2525
valueToParse
@@ -49,15 +49,51 @@ module ValueOption =
4949
/// <param name="voption1">The input option</param>
5050
/// <param name="voption2">The input option</param>
5151
/// <returns></returns>
52-
let zip (voption1: 'a voption) (voption2: 'b voption) =
52+
let inline zip (voption1: 'a voption) (voption2: 'b voption) =
5353
match voption1, voption2 with
5454
| ValueSome v1, ValueSome v2 -> ValueSome(v1, v2)
5555
| _ -> ValueNone
5656

5757

58-
let ofResult =
59-
function
58+
let inline ofResult (result: Result<_, _>) =
59+
match result with
6060
| Ok v -> ValueSome v
6161
| Error _ -> ValueNone
6262

63+
64+
/// <summary>
65+
/// Convert a potentially null value to an ValueOption.
66+
///
67+
/// This is different from <see cref="FSharp.Core.ValueOption.ofObj">ValueOption.ofObj</see> where it doesn't require the value to be constrained to null.
68+
/// This is beneficial where third party APIs may generate a record type using reflection and it can be null.
69+
/// See <a href="https://latkin.org/blog/2015/05/18/null-checking-considerations-in-f-its-harder-than-you-think/">Null-checking considerations in F#</a> for more details.
70+
/// </summary>
71+
/// <param name="value">The potentially null value</param>
72+
/// <returns>An ValueOption</returns>
73+
/// <seealso cref="FSharp.Core.ValueOption.ofObj"/>
74+
let inline ofNull (value: 'nullableValue) =
75+
if System.Object.ReferenceEquals(value, null) then
76+
ValueNone
77+
else
78+
ValueSome value
79+
80+
81+
/// <summary>
82+
///
83+
/// <c>bindNull binder voption</c> evaluates to <c>match voption with ValueNone -> ValueNone | ValueSome x -> binder x |> ValueOption.ofNull</c>
84+
///
85+
/// Automatically onverts the result of binder that is pontentially null into an Valueoption.
86+
/// </summary>
87+
/// <param name="binder">A function that takes the value of type 'value from an voption and transforms it into
88+
/// a value of type 'nullableValue.</param>
89+
/// <param name="voption">The input voption</param>
90+
/// <typeparam name="'value"></typeparam>
91+
/// <typeparam name="'nullableValue"></typeparam>
92+
/// <returns>A voption of the output type of the binder.</returns>
93+
/// <seealso cref="ofNull"/>
94+
let inline bindNull (binder: 'value -> 'nullableValue) (voption: ValueOption<'value>) =
95+
match voption with
96+
| ValueSome x -> binder x |> ofNull
97+
| ValueNone -> ValueNone
98+
6399
#endif

tests/FsToolkit.ErrorHandling.Tests/Option.fs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,43 @@ let ofResultTests =
8484
Expect.equal (Option.ofResult (Error "x")) None "Error _" ]
8585

8686

87+
let ofNullTests =
88+
testList
89+
"Option.ofNull Tests"
90+
[ testCase "A not null value"
91+
<| fun _ ->
92+
let someValue = "hello"
93+
Expect.equal (Option.ofNull someValue) (Some someValue) ""
94+
testCase "A null value"
95+
<| fun _ ->
96+
let (someValue: string) = null
97+
Expect.equal (Option.ofNull someValue) (None) "" ]
98+
99+
let bindNullTests =
100+
testList
101+
"Option.bindNull Tests"
102+
[ testCase "Some notNull"
103+
<| fun _ ->
104+
let value1 = Some "world"
105+
let someBinder _ = "hello"
106+
Expect.equal (Option.bindNull someBinder value1) (Some "hello") ""
107+
testCase "Some null"
108+
<| fun _ ->
109+
let value1 = Some "world"
110+
let someBinder _ = null
111+
Expect.equal (Option.bindNull someBinder value1) (None) ""
112+
testCase "None"
113+
<| fun _ ->
114+
let value1 = None
115+
let someBinder _ = "won't hit here"
116+
Expect.equal (Option.bindNull someBinder value1) (None) "" ]
117+
87118
let allTests =
88119
testList
89120
"Option Tests"
90121
[ traverseResultTests
91122
tryParseTests
92123
tryGetValueTests
93-
ofResultTests ]
124+
ofResultTests
125+
ofNullTests
126+
bindNullTests ]

tests/FsToolkit.ErrorHandling.Tests/ValueOption.fs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,46 @@ let ofResultTests =
8787
Expect.equal (ValueOption.ofResult (Ok "abc")) (ValueSome "abc") "Ok string"
8888
Expect.equal (ValueOption.ofResult (Error "x")) ValueNone "Error _" ]
8989

90+
let ofNullTests =
91+
testList
92+
"ValueOption.ofNull Tests"
93+
[ testCase "A not null value"
94+
<| fun _ ->
95+
let someValue = "hello"
96+
Expect.equal (ValueOption.ofNull someValue) (ValueSome someValue) ""
97+
testCase "A null value"
98+
<| fun _ ->
99+
let (someValue: string) = null
100+
Expect.equal (ValueOption.ofNull someValue) (ValueNone) "" ]
101+
102+
let bindNullTests =
103+
testList
104+
"ValueOption.bindNull Tests"
105+
[ testCase "ValueSome notNull"
106+
<| fun _ ->
107+
let value1 = ValueSome "world"
108+
let someBinder _ = "hello"
109+
Expect.equal (ValueOption.bindNull someBinder value1) (ValueSome "hello") ""
110+
testCase "ValueSome null"
111+
<| fun _ ->
112+
let value1 = ValueSome "world"
113+
let someBinder _ = null
114+
Expect.equal (ValueOption.bindNull someBinder value1) (ValueNone) ""
115+
testCase "ValueNone"
116+
<| fun _ ->
117+
let value1 = ValueNone
118+
let someBinder _ = "won't hit here"
119+
Expect.equal (ValueOption.bindNull someBinder value1) (ValueNone) "" ]
90120

91121
let allTests =
92122
testList
93123
"ValueOption Tests"
94124
[ traverseResultTests
95125
tryParseTests
96126
tryGetValueTests
97-
ofResultTests ]
127+
ofResultTests
128+
ofNullTests
129+
bindNullTests ]
98130
#else
99131

100132
let allTests = testList "ValueOption Tests" []

0 commit comments

Comments
 (0)