Skip to content

Commit 156e79e

Browse files
authored
ValueOption support (#131)
* Adds ValueOption support * Updates FunctionMap and put output into README
1 parent 6fc7153 commit 156e79e

File tree

14 files changed

+1062
-122
lines changed

14 files changed

+1062
-122
lines changed

paket.dependencies

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ source https://api.nuget.org/v3/index.json
22

33

44
storage: none
5-
nuget FSharp.Core 4.6.2
5+
nuget FSharp.Core 4.7.0
66
nuget Hopac
77
nuget Microsoft.SourceLink.GitHub prerelease copy_local: true
88
nuget Ply
99

1010
group Test
1111
source https://api.nuget.org/v3/index.json
1212
storage: none
13+
nuget FSharp.Core 4.7.0
1314
nuget Expecto
1415
nuget Expecto.Hopac
1516
nuget Fable.Mocha 2.10.0

paket.lock

Lines changed: 31 additions & 96 deletions
Large diffs are not rendered by default.

src/FsToolkit.ErrorHandling/FsToolkit.ErrorHandling.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
<Compile Include="ValidationCE.fs" />
2828
<Compile Include="Option.fs" />
2929
<Compile Include="OptionCE.fs" />
30+
<Compile Include="ValueOption.fs" />
31+
<Compile Include="ValueOptionCE.fs" />
3032
<Compile Include="AsyncOption.fs" />
3133
<Compile Include="AsyncOptionCE.fs" />
3234
<Compile Include="AsyncOptionOp.fs" />

src/FsToolkit.ErrorHandling/Option.fs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ namespace FsToolkit.ErrorHandling
33
[<RequireQualifiedAccess>]
44
module Option =
55

6+
let ofValueOption (vopt: _ voption) =
7+
match vopt with
8+
| ValueSome v -> Some v
9+
| ValueNone -> None
10+
11+
let toValueOption (opt: _ option) =
12+
match opt with
13+
| Some v -> ValueSome v
14+
| None -> ValueNone
15+
616
let traverseResult f opt =
717
match opt with
818
| None -> Ok None
@@ -30,11 +40,12 @@ module Option =
3040
/// <param name="option1">The input option</param>
3141
/// <param name="option2">The input option</param>
3242
/// <returns></returns>
33-
let zip (option1: option<'a>) (option2: option<'b>) =
43+
let zip (option1: 'a option) (option2: 'b option) =
3444
match option1, option2 with
3545
| Some v1, Some v2 -> Some(v1, v2)
3646
| _ -> None
3747

48+
3849
let ofResult =
3950
function
4051
| Ok v -> Some v

src/FsToolkit.ErrorHandling/OptionCE.fs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,12 @@ module OptionCE =
6767
member inline _.Source(result: _ option) : _ option = result
6868

6969

70-
let option = OptionBuilder()
70+
// /// <summary>
71+
// /// Method lets us transform data types into our internal representation.
72+
// /// </summary>
73+
member inline _.Source(vopt: ValueOption<_>) : Option<_> = vopt |> Option.ofValueOption
7174

75+
let option = OptionBuilder()
7276

7377
[<AutoOpen>]
7478
// Having members as extensions gives them lower priority in
@@ -102,7 +106,6 @@ module OptionExtensions =
102106
/// </summary>
103107
member inline _.Source(s: #seq<_>) = s
104108

105-
106109
// /// <summary>
107110
// /// Method lets us transform data types into our internal representation.
108111
// /// </summary>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
namespace FsToolkit.ErrorHandling
2+
3+
#if !FABLE_COMPILER
4+
[<RequireQualifiedAccess>]
5+
module ValueOption =
6+
7+
let ofOption (opt: 'a option) =
8+
match opt with
9+
| Some v -> ValueSome v
10+
| None -> ValueNone
11+
12+
let toOption (vopt: 'a voption) =
13+
match vopt with
14+
| ValueSome v -> Some v
15+
| ValueNone -> None
16+
17+
let traverseResult f vopt =
18+
match vopt with
19+
| ValueNone -> Ok ValueNone
20+
| ValueSome v -> f v |> Result.map ValueSome
21+
22+
let sequenceResult opt = traverseResult id opt
23+
24+
let inline tryParse< ^T when ^T: (static member TryParse : string * byref< ^T > -> bool) and ^T: (new : unit -> ^T)>
25+
valueToParse
26+
=
27+
let mutable output = new ^T()
28+
29+
let parsed =
30+
(^T: (static member TryParse : string * byref< ^T > -> bool) (valueToParse, &output))
31+
32+
match parsed with
33+
| true -> ValueSome output
34+
| _ -> ValueNone
35+
36+
/// <summary>
37+
/// Takes two voptions and returns a tuple of the pair or none if either are none
38+
/// </summary>
39+
/// <param name="voption1">The input option</param>
40+
/// <param name="voption2">The input option</param>
41+
/// <returns></returns>
42+
let zip (voption1: 'a voption) (voption2: 'b voption) =
43+
match voption1, voption2 with
44+
| ValueSome v1, ValueSome v2 -> ValueSome(v1, v2)
45+
| _ -> ValueNone
46+
47+
48+
let ofResult =
49+
function
50+
| Ok v -> ValueSome v
51+
| Error _ -> ValueNone
52+
53+
#endif
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
namespace FsToolkit.ErrorHandling
2+
3+
4+
#if !FABLE_COMPILER
5+
[<AutoOpen>]
6+
module ValueOptionCE =
7+
open System
8+
9+
type ValueOptionBuilder() =
10+
member inline _.Return(x) = ValueSome x
11+
12+
member inline _.ReturnFrom(m: 'T voption) = m
13+
14+
member inline _.Bind(m: 'a voption, f: 'a -> 'b voption) = ValueOption.bind f m
15+
16+
// Could not get it to work solely with Source. In loop cases it would potentially match the #seq overload and ask for type annotation
17+
member this.Bind(m: 'a when 'a: null, f: 'a -> 'b voption) = this.Bind(m |> ValueOption.ofObj, f)
18+
19+
member inline this.Zero() = this.Return()
20+
21+
member inline _.Combine(m, f) = ValueOption.bind f m
22+
member inline this.Combine(m1: _ voption, m2: _ voption) = this.Bind(m1, (fun () -> m2))
23+
24+
member inline _.Delay(f: unit -> _) = f
25+
26+
member inline _.Run(f) = f ()
27+
28+
member inline this.TryWith(m, h) =
29+
try
30+
this.Run m
31+
with
32+
| e -> h e
33+
34+
member inline this.TryFinally(m, compensation) =
35+
try
36+
this.Run m
37+
finally
38+
compensation ()
39+
40+
member inline this.Using(resource: 'T :> IDisposable, binder) : _ voption =
41+
this.TryFinally(
42+
(fun () -> binder resource),
43+
(fun () ->
44+
if not <| obj.ReferenceEquals(resource, null) then
45+
resource.Dispose())
46+
)
47+
48+
member this.While(guard: unit -> bool, generator: unit -> _ voption) : _ voption =
49+
if not <| guard () then
50+
this.Zero()
51+
else
52+
this.Bind(this.Run generator, (fun () -> this.While(guard, generator)))
53+
54+
member inline this.For(sequence: #seq<'T>, binder: 'T -> _ voption) : _ voption =
55+
this.Using(
56+
sequence.GetEnumerator(),
57+
fun enum -> this.While(enum.MoveNext, this.Delay(fun () -> binder enum.Current))
58+
)
59+
60+
member inline _.BindReturn(x, f) = ValueOption.map f x
61+
62+
member inline _.BindReturn(x, f) =
63+
x |> ValueOption.ofObj |> ValueOption.map f
64+
65+
member inline _.MergeSources(option1, option2) = ValueOption.zip option1 option2
66+
67+
/// <summary>
68+
/// Method lets us transform data types into our internal representation. This is the identity method to recognize the self type.
69+
///
70+
/// See https://stackoverflow.com/questions/35286541/why-would-you-use-builder-source-in-a-custom-computation-expression-builder
71+
/// </summary>
72+
member inline _.Source(result: _ voption) : _ voption = result
73+
74+
75+
// /// <summary>
76+
// /// Method lets us transform data types into our internal representation.
77+
// /// </summary>
78+
member inline _.Source(vopt: _ option) : _ voption = vopt |> ValueOption.ofOption
79+
80+
let voption = ValueOptionBuilder()
81+
82+
[<AutoOpen>]
83+
// Having members as extensions gives them lower priority in
84+
// overload resolution and allows skipping more type annotations.
85+
module ValueOptionExtensionsLower =
86+
type ValueOptionBuilder with
87+
member inline _.Source(nullableObj: 'a when 'a: null) = nullableObj |> ValueOption.ofObj
88+
member inline _.Source(m: string) = m |> ValueOption.ofObj
89+
90+
member inline _.MergeSources(nullableObj1, option2) =
91+
ValueOption.zip (ValueOption.ofObj nullableObj1) option2
92+
93+
94+
member inline _.MergeSources(option1, nullableObj2) =
95+
ValueOption.zip (option1) (ValueOption.ofObj nullableObj2)
96+
97+
98+
member inline _.MergeSources(nullableObj1, nullableObj2) =
99+
ValueOption.zip (ValueOption.ofObj nullableObj1) (ValueOption.ofObj nullableObj2)
100+
101+
[<AutoOpen>]
102+
// Having members as extensions gives them lower priority in
103+
// overload resolution and allows skipping more type annotations.
104+
// The later declared, the higher than previous extension methods, this is magic
105+
module ValueOptionExtensions =
106+
open System
107+
108+
type ValueOptionBuilder with
109+
/// <summary>
110+
/// Needed to allow `for..in` and `for..do` functionality
111+
/// </summary>
112+
member inline _.Source(s: #seq<_>) = s
113+
114+
// /// <summary>
115+
// /// Method lets us transform data types into our internal representation.
116+
// /// </summary>
117+
member inline _.Source(nullable: Nullable<'a>) : 'a voption = nullable |> ValueOption.ofNullable
118+
#endif

tests/FsToolkit.ErrorHandling.Tests/FsToolkit.ErrorHandling.Tests.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
<Compile Include="ResultOption.fs" />
2121
<Compile Include="Option.fs" />
2222
<Compile Include="OptionCE.fs" />
23+
<Compile Include="ValueOption.fs" />
24+
<Compile Include="ValueOptionCE.fs" />
2325
<Compile Include="AsyncOption.fs" />
2426
<Compile Include="AsyncOptionCE.fs" />
2527
<Compile Include="List.fs" />

tests/FsToolkit.ErrorHandling.Tests/Main.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ let allTests =
1414
ResultOptionTests.allTests
1515
OptionTests.allTests
1616
OptionCETests.allTests
17+
#if !FABLE_COMPILER
18+
ValueOptionTests.allTests
19+
ValueOptionCETests.allTests
20+
#endif
1721
AsyncOptionTests.allTests
1822
AsyncOptionCETests.allTests
1923
ListTests.allTests

tests/FsToolkit.ErrorHandling.Tests/OptionCE.fs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ let ceTests =
7878
try
7979
return data
8080
with
81-
| e -> return! raise e
81+
| e -> return raise e
8282
}
8383

8484
Expect.equal actual (Some data) "Try with failed"
@@ -442,7 +442,29 @@ let ``OptionCE applicative tests`` =
442442
return a + b - c
443443
}
444444

445-
Expect.equal actual None "Should be None" ]
445+
Expect.equal actual None "Should be None"
446+
447+
testCase "ValueOption.Some"
448+
<| fun () ->
449+
let actual =
450+
option {
451+
let! a = ValueSome 3
452+
return a
453+
}
454+
455+
Expect.equal actual (Some 3) "Should be None"
456+
457+
testCase "ValueOption.None"
458+
<| fun () ->
459+
let actual =
460+
option {
461+
let! a = ValueNone
462+
return a
463+
}
464+
465+
Expect.equal actual (None) "Should be None" ]
466+
467+
446468

447469
let allTests =
448470
testList

0 commit comments

Comments
 (0)