From 96bdae23868308300e64f848436d7003ccf8cee1 Mon Sep 17 00:00:00 2001 From: Marcus Griep Date: Fri, 18 Nov 2016 16:15:57 -0500 Subject: [PATCH 1/2] Add Option.apply This PR is a proposed addition to the Option module. Relevant discussion can be found on RFC FS-1007 and fsharp/fslang-design#60. --- .../Microsoft.FSharp.Core/OptionModule.fs | 50 +++++++++++++++++++ .../SurfaceArea.coreclr.fs | 1 + .../SurfaceArea.net40.fs | 1 + .../SurfaceArea.portable259.fs | 1 + .../SurfaceArea.portable47.fs | 1 + .../SurfaceArea.portable7.fs | 1 + .../SurfaceArea.portable78.fs | 1 + src/fsharp/FSharp.Core/option.fs | 6 +++ src/fsharp/FSharp.Core/option.fsi | 7 +++ 9 files changed, 69 insertions(+) diff --git a/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/OptionModule.fs b/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/OptionModule.fs index b2010567f13c..efe6a557933e 100644 --- a/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/OptionModule.fs +++ b/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/OptionModule.fs @@ -18,6 +18,56 @@ Make sure each method works on: [] type OptionModule() = + [] + member this.Apply () = + let oneMinus x = 1 - x + Assert.AreEqual(Option.apply None None, None) + Assert.AreEqual(Option.apply (Some 2) None, None) + Assert.AreEqual(Option.apply None (Some oneMinus), None) + Assert.AreEqual(Option.apply (Some 2) (Some oneMinus), Some -1) + + let afterX x = "x" + x + Assert.AreEqual(Option.apply None None, None) + Assert.AreEqual(Option.apply (Some "y") None, None) + Assert.AreEqual(Option.apply None (Some afterX), None) + Assert.AreEqual(Option.apply (Some "y") (Some afterX), Some "xy") + + let lengthPlus3 x = 3 + String.length x + Assert.AreEqual(Option.apply None (Some lengthPlus3), None) + Assert.AreEqual(Option.apply (Some "y") (Some lengthPlus3), Some 4) + + [] + member this.MapPipeApply () = + let add3 x y z = string x + string y + string z + Assert.AreEqual(Option.map add3 None |> Option.apply None |> Option.apply None, None) + Assert.AreEqual(Option.map add3 None |> Option.apply (Some 2) |> Option.apply None, None) + Assert.AreEqual(Option.map add3 (Some 1) |> Option.apply None |> Option.apply None, None) + Assert.AreEqual(Option.map add3 (Some 1) |> Option.apply (Some 2) |> Option.apply None, None) + Assert.AreEqual(Option.map add3 None |> Option.apply None |> Option.apply (Some 3), None) + Assert.AreEqual(Option.map add3 None |> Option.apply (Some 2) |> Option.apply (Some 3), None) + Assert.AreEqual(Option.map add3 (Some 1) |> Option.apply None |> Option.apply (Some 3), None) + Assert.AreEqual(Option.map add3 (Some 1) |> Option.apply (Some 2) |> Option.apply (Some 3), Some "123") + + let concat3 x y z = x + y + z + Assert.AreEqual(Option.map concat3 None |> Option.apply None |> Option.apply None, None) + Assert.AreEqual(Option.map concat3 None |> Option.apply (Some "y") |> Option.apply None, None) + Assert.AreEqual(Option.map concat3 (Some "x") |> Option.apply None |> Option.apply None, None) + Assert.AreEqual(Option.map concat3 (Some "x") |> Option.apply (Some "y") |> Option.apply None, None) + Assert.AreEqual(Option.map concat3 None |> Option.apply None |> Option.apply (Some "z"), None) + Assert.AreEqual(Option.map concat3 None |> Option.apply (Some "y") |> Option.apply (Some "z"), None) + Assert.AreEqual(Option.map concat3 (Some "x") |> Option.apply None |> Option.apply (Some "z"), None) + Assert.AreEqual(Option.map concat3 (Some "x") |> Option.apply (Some "y") |> Option.apply (Some "z"), Some "xyz") + + let mix3 x y z = String.length x * y + int32 z + Assert.AreEqual(Option.map mix3 None |> Option.apply None |> Option.apply None, None) + Assert.AreEqual(Option.map mix3 None |> Option.apply (Some 6) |> Option.apply None, None) + Assert.AreEqual(Option.map mix3 (Some "asdwxf") |> Option.apply None |> Option.apply None, None) + Assert.AreEqual(Option.map mix3 (Some "asdwxf") |> Option.apply (Some 6) |> Option.apply None, None) + Assert.AreEqual(Option.map mix3 None |> Option.apply None |> Option.apply (Some 6UL), None) + Assert.AreEqual(Option.map mix3 None |> Option.apply (Some 6) |> Option.apply (Some 6UL), None) + Assert.AreEqual(Option.map mix3 (Some "asdwxf") |> Option.apply None |> Option.apply (Some 6UL), None) + Assert.AreEqual(Option.map mix3 (Some "asdwxf") |> Option.apply (Some 6) |> Option.apply (Some 6UL), Some 42) + [] member this.FilterSomeIntegerWhenPredicateReturnsTrue () = let test x = diff --git a/src/fsharp/FSharp.Core.Unittests/SurfaceArea.coreclr.fs b/src/fsharp/FSharp.Core.Unittests/SurfaceArea.coreclr.fs index 10c4389cb951..e1c8cbd06c38 100644 --- a/src/fsharp/FSharp.Core.Unittests/SurfaceArea.coreclr.fs +++ b/src/fsharp/FSharp.Core.Unittests/SurfaceArea.coreclr.fs @@ -2721,6 +2721,7 @@ Microsoft.FSharp.Core.OptionModule: Boolean IsSome[T](Microsoft.FSharp.Core.FSha Microsoft.FSharp.Core.OptionModule: Int32 Count[T](Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Int32 GetHashCode() Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Collections.FSharpList`1[T] ToList[T](Microsoft.FSharp.Core.FSharpOption`1[T]) +Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Apply[T,TResult](Microsoft.FSharp.Core.FSharpOption`1[T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[T,TResult]]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Bind[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpOption`1[TResult]], Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[T] Filter[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[T]) diff --git a/src/fsharp/FSharp.Core.Unittests/SurfaceArea.net40.fs b/src/fsharp/FSharp.Core.Unittests/SurfaceArea.net40.fs index c452c3f31c44..d4813f8f97f3 100644 --- a/src/fsharp/FSharp.Core.Unittests/SurfaceArea.net40.fs +++ b/src/fsharp/FSharp.Core.Unittests/SurfaceArea.net40.fs @@ -2755,6 +2755,7 @@ Microsoft.FSharp.Core.OptionModule: Boolean IsSome[T](Microsoft.FSharp.Core.FSha Microsoft.FSharp.Core.OptionModule: Int32 Count[T](Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Int32 GetHashCode() Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Collections.FSharpList`1[T] ToList[T](Microsoft.FSharp.Core.FSharpOption`1[T]) +Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Apply[T,TResult](Microsoft.FSharp.Core.FSharpOption`1[T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[T,TResult]]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Bind[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpOption`1[TResult]], Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[T] Filter[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[T]) diff --git a/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable259.fs b/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable259.fs index 8e5337ef4a83..286bf5e4869e 100644 --- a/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable259.fs +++ b/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable259.fs @@ -2726,6 +2726,7 @@ Microsoft.FSharp.Core.OptionModule: Boolean IsSome[T](Microsoft.FSharp.Core.FSha Microsoft.FSharp.Core.OptionModule: Int32 Count[T](Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Int32 GetHashCode() Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Collections.FSharpList`1[T] ToList[T](Microsoft.FSharp.Core.FSharpOption`1[T]) +Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Apply[T,TResult](Microsoft.FSharp.Core.FSharpOption`1[T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[T,TResult]]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Bind[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpOption`1[TResult]], Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[T] Filter[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[T]) diff --git a/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable47.fs b/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable47.fs index 531eebf0e8f5..22c7106fb3b7 100644 --- a/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable47.fs +++ b/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable47.fs @@ -2727,6 +2727,7 @@ Microsoft.FSharp.Core.OptionModule: Boolean IsSome[T](Microsoft.FSharp.Core.FSha Microsoft.FSharp.Core.OptionModule: Int32 Count[T](Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Int32 GetHashCode() Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Collections.FSharpList`1[T] ToList[T](Microsoft.FSharp.Core.FSharpOption`1[T]) +Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Apply[T,TResult](Microsoft.FSharp.Core.FSharpOption`1[T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[T,TResult]]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Bind[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpOption`1[TResult]], Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[T] Filter[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[T]) diff --git a/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable7.fs b/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable7.fs index 7d320ad84830..f0f4c22d23d1 100644 --- a/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable7.fs +++ b/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable7.fs @@ -2739,6 +2739,7 @@ Microsoft.FSharp.Core.OptionModule: Boolean IsSome[T](Microsoft.FSharp.Core.FSha Microsoft.FSharp.Core.OptionModule: Int32 Count[T](Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Int32 GetHashCode() Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Collections.FSharpList`1[T] ToList[T](Microsoft.FSharp.Core.FSharpOption`1[T]) +Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Apply[T,TResult](Microsoft.FSharp.Core.FSharpOption`1[T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[T,TResult]]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Bind[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpOption`1[TResult]], Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[T] Filter[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[T]) diff --git a/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable78.fs b/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable78.fs index f731fcdbcc07..9c9d3d60fe76 100644 --- a/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable78.fs +++ b/src/fsharp/FSharp.Core.Unittests/SurfaceArea.portable78.fs @@ -2726,6 +2726,7 @@ Microsoft.FSharp.Core.OptionModule: Boolean IsSome[T](Microsoft.FSharp.Core.FSha Microsoft.FSharp.Core.OptionModule: Int32 Count[T](Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Int32 GetHashCode() Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Collections.FSharpList`1[T] ToList[T](Microsoft.FSharp.Core.FSharpOption`1[T]) +Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Apply[T,TResult](Microsoft.FSharp.Core.FSharpOption`1[T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[T,TResult]]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Bind[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpOption`1[TResult]], Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[TResult] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], Microsoft.FSharp.Core.FSharpOption`1[T]) Microsoft.FSharp.Core.OptionModule: Microsoft.FSharp.Core.FSharpOption`1[T] Filter[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[T]) diff --git a/src/fsharp/FSharp.Core/option.fs b/src/fsharp/FSharp.Core/option.fs index 3bca435ce6bb..47d56c748ff7 100644 --- a/src/fsharp/FSharp.Core/option.fs +++ b/src/fsharp/FSharp.Core/option.fs @@ -37,6 +37,12 @@ namespace Microsoft.FSharp.Core [] let map f inp = match inp with None -> None | Some x -> Some (f x) + [] + let apply inp fOpt = + match fOpt, inp with + | Some f, Some x -> Some (f x) + | _ -> None + [] let bind f inp = match inp with None -> None | Some x -> f x diff --git a/src/fsharp/FSharp.Core/option.fsi b/src/fsharp/FSharp.Core/option.fsi index 3225893e2844..922c878e2c37 100644 --- a/src/fsharp/FSharp.Core/option.fsi +++ b/src/fsharp/FSharp.Core/option.fsi @@ -85,6 +85,13 @@ namespace Microsoft.FSharp.Core [] val map: mapping:('T -> 'U) -> option:'T option -> 'U option + /// apply inp fOpt evaluates to match inp, fOpt with Some x, f -> Some (f x) | _ -> None. + /// The input option. + /// An optional function to apply to the option value. + /// An option of the input value after applying the mapping function, or None if either the input or the mapping function is None. + [] + val apply: option:'T option -> mappingOpt:('T -> 'U) option -> 'U option + /// bind f inp evaluates to match inp with None -> None | Some x -> f x /// A function that takes the value of type T from an option and transforms it into /// an option containing a value of type U. From 0362313f0f2e9eab763a68ff6411c94f5d0121c5 Mon Sep 17 00:00:00 2001 From: Marcus Griep Date: Fri, 18 Nov 2016 16:35:10 -0500 Subject: [PATCH 2/2] Add tests on Option module properties --- .../FSharp.Core/Microsoft.FSharp.Core/OptionModule.fs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/OptionModule.fs b/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/OptionModule.fs index efe6a557933e..5a5c49513fac 100644 --- a/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/OptionModule.fs +++ b/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/OptionModule.fs @@ -68,6 +68,16 @@ type OptionModule() = Assert.AreEqual(Option.map mix3 (Some "asdwxf") |> Option.apply None |> Option.apply (Some 6UL), None) Assert.AreEqual(Option.map mix3 (Some "asdwxf") |> Option.apply (Some 6) |> Option.apply (Some 6UL), Some 42) + [] + member this.MapBindApplyEquivalenceProperties () = + let fn x = x + 3 + Assert.AreEqual(Option.map fn None, Option.bind (fn >> Some) None) + Assert.AreEqual(Option.map fn (Some 5), Option.bind (fn >> Some) (Some 5)) + Assert.AreEqual(Option.apply None None, (fun xOpt -> Option.bind (fun f -> Option.map f xOpt)) None None) + Assert.AreEqual(Option.apply (Some 5) None, (fun xOpt -> Option.bind (fun f -> Option.map f xOpt)) (Some 5) None) + Assert.AreEqual(Option.apply None (Some fn), (fun xOpt -> Option.bind (fun f -> Option.map f xOpt)) None (Some fn)) + Assert.AreEqual(Option.apply (Some 5) (Some fn), (fun xOpt -> Option.bind (fun f -> Option.map f xOpt)) (Some 5) (Some fn)) + [] member this.FilterSomeIntegerWhenPredicateReturnsTrue () = let test x =