diff --git a/docsrc/content/abstraction-foldable.fsx b/docsrc/content/abstraction-foldable.fsx index a7dfe0c1e..dbaa52b33 100644 --- a/docsrc/content/abstraction-foldable.fsx +++ b/docsrc/content/abstraction-foldable.fsx @@ -58,6 +58,8 @@ From .Net/F# - ``seq<'T>`` - ``list<'T>`` - ``'T []`` + - ``Set<'T>`` + - ``HashSet<'T>`` - ``option<'T>`` - ``voption<'T>`` - ``ResizeArray<'T>`` diff --git a/docsrc/content/abstraction-functor.fsx b/docsrc/content/abstraction-functor.fsx index b41e8a0b6..c30a9311e 100644 --- a/docsrc/content/abstraction-functor.fsx +++ b/docsrc/content/abstraction-functor.fsx @@ -104,6 +104,7 @@ Restricted: - ``string`` - ``StringBuilder`` - ``Set<'T>`` + - ``HashSet<'T>`` - ``IEnumerator<'T>`` [Suggest another](https://github.com/fsprojects/FSharpPlus/issues/new) concrete implementation diff --git a/docsrc/content/abstraction-monoid.fsx b/docsrc/content/abstraction-monoid.fsx index e79d881a9..0f760af40 100644 --- a/docsrc/content/abstraction-monoid.fsx +++ b/docsrc/content/abstraction-monoid.fsx @@ -57,6 +57,7 @@ From .Net/F# - ``StringBuilder`` - ``unit`` - ``Set<'T>`` + - ``HashSet<'T>`` - ``Map<'T,'Monoid>`` - ``TimeSpan`` - ``Tuple<'Monoid1* ... *'MonoidN>`` diff --git a/docsrc/content/abstraction-semigroup.fsx b/docsrc/content/abstraction-semigroup.fsx index f80d67107..666ab6f73 100644 --- a/docsrc/content/abstraction-semigroup.fsx +++ b/docsrc/content/abstraction-semigroup.fsx @@ -42,6 +42,7 @@ From .Net/F# - ``StringBuilder`` - ``unit`` - ``Set<'T>`` + - ``HashSet<'T>`` - ``Map<'T, 'U>`` - ``TimeSpan`` - ``Tuple<*>`` diff --git a/src/FSharpPlus/Control/Functor.fs b/src/FSharpPlus/Control/Functor.fs index 33857b3e5..f4af8f245 100644 --- a/src/FSharpPlus/Control/Functor.fs +++ b/src/FSharpPlus/Control/Functor.fs @@ -106,6 +106,7 @@ type Map = static member Map ((x: StringBuilder , f ), _mthd: Map) = StringBuilder (String.map f (string x)) static member Map ((x: Set<_> , f ), _mthd: Map) = Set.map f x static member Map ((_: Set2<'T> , _: 'T->'U), _mthd: Map) = Set2<'U>() + static member Map ((x: HashSet<_ > , f ), _mthd: Map) = HashSet.map f x static member inline Invoke (mapping: 'T->'U) (source: '``Functor<'T>``) : '``Functor<'U>`` = diff --git a/src/FSharpPlus/Control/Monad.fs b/src/FSharpPlus/Control/Monad.fs index 2260364ec..bd0ea8f2a 100644 --- a/src/FSharpPlus/Control/Monad.fs +++ b/src/FSharpPlus/Control/Monad.fs @@ -167,7 +167,7 @@ type Return = static member Return (_: string , _: Return ) = fun (x: char) -> string x : string static member Return (_: StringBuilder , _: Return ) = fun (x: char) -> new StringBuilder (string x) : StringBuilder static member Return (_: 'a Set , _: Return ) = fun (x: 'a ) -> Set.singleton x - static member Return (_: 'a Set2 , _: Return ) = fun (_: 'a ) -> Set2() : 'a Set2 + static member Return (_: 'a HashSet , _: Return ) = fun (x: 'a ) -> HashSet.singleton x type Delay = diff --git a/src/FSharpPlus/Control/Monoid.fs b/src/FSharpPlus/Control/Monoid.fs index 69c0d4ec2..184ab1872 100644 --- a/src/FSharpPlus/Control/Monoid.fs +++ b/src/FSharpPlus/Control/Monoid.fs @@ -29,6 +29,7 @@ type Plus = static member ``+`` (x: Set<_> , y , []_mthd: Plus ) = Set.union x y #if !FABLE_COMPILER + static member ``+`` (x: HashSet<_> , y , []_mthd: Plus ) = HashSet.union x y static member ``+`` (x: StringBuilder , y: StringBuilder , []_mthd: Plus ) = StringBuilder().Append(x).Append(y) static member ``+`` (_: Id0 , _: Id0 , []_mthd: Plus ) = Id0 "" static member ``+`` (x: AggregateException, y: AggregateException, []_mthd: Plus ) = Exception.add x y diff --git a/src/FSharpPlus/Control/Numeric.fs b/src/FSharpPlus/Control/Numeric.fs index ff300799f..fead29930 100644 --- a/src/FSharpPlus/Control/Numeric.fs +++ b/src/FSharpPlus/Control/Numeric.fs @@ -6,6 +6,7 @@ namespace FSharpPlus.Control open System.Runtime.InteropServices open FSharpPlus.Internals +open FSharpPlus #if FABLE_COMPILER /// NOTE type OptionalAttribute ()= @@ -206,6 +207,7 @@ type Zero with static member inline Zero (_: Lazy<'a> , _: Zero) = let (v: 'a) = Zero.Invoke () in lazy v static member Zero (_: Dictionary<'a,'b> , _: Zero) = Dictionary<'a,'b> () static member Zero (_: ResizeArray<'a> , _: Zero) = ResizeArray () : ResizeArray<'a> + static member Zero (_: HashSet<'a> , _: Zero ) = HashSet.empty : HashSet<'a> type Zero with static member inline Zero (_: ^R , _: Default6) = FromInt64.Invoke 0L : ^R @@ -213,7 +215,7 @@ type Zero with static member inline Zero (_: ^R , _: Default5) = Implicit.Invoke 0 : ^R static member Zero (_: seq<'a> , _: Default4) = Seq.empty : seq<'a> - static member Zero (_: IEnumerator<'a> , _: Default4) = FSharpPlus.Enumerator.Empty () : IEnumerator<'a> + static member Zero (_: IEnumerator<'a> , _: Default4) = Enumerator.Empty () : IEnumerator<'a> static member Zero (_: IDictionary<'a,'b> , _: Default4) = Dictionary<'a,'b> () :> IDictionary<'a,'b> static member Zero (_: IReadOnlyDictionary<'a,'b> , _: Default4) = Dictionary<'a,'b> () :> IReadOnlyDictionary<'a,'b> static member inline Zero (_: 't , _: Default3) = (^t : (static member Empty: ^t) ()) : 't diff --git a/src/FSharpPlus/Control/ZipApplicative.fs b/src/FSharpPlus/Control/ZipApplicative.fs index 760b68151..5990d91d9 100644 --- a/src/FSharpPlus/Control/ZipApplicative.fs +++ b/src/FSharpPlus/Control/ZipApplicative.fs @@ -61,14 +61,14 @@ type Pure = [.", Code, IsError = true)>] static member Pure (x: ResizeArray<'a>, _: Pure ) = Return.Return (x, Unchecked.defaultof) - //Restricted [] static member Pure (_: string , _: Pure ) = fun (x: char) -> string x : string [] static member Pure (_: StringBuilder , _: Pure ) = fun (x: char) -> new StringBuilder (string x) : StringBuilder [] static member Pure (_: 'a Set , _: Pure ) = fun (x: 'a ) -> Set.singleton x - static member Pure (_: 'a Set2 , _: Pure ) = fun (_: 'a ) -> Set2() : 'a Set2 + [] + static member Pure (_: 'a HashSet , _: Pure ) = fun (x: 'a ) -> HashSet.singleton x #endif diff --git a/src/FSharpPlus/Extensions/HashSet.fs b/src/FSharpPlus/Extensions/HashSet.fs new file mode 100644 index 000000000..f3154b278 --- /dev/null +++ b/src/FSharpPlus/Extensions/HashSet.fs @@ -0,0 +1,57 @@ +namespace FSharpPlus + +/// Additional operations on HashSet<'T> +[] +module HashSet = + open System.Collections.Generic + open FSharpPlus.Internals.Errors + + /// The empty set for the type 'T. + [] + [] + let empty<'T> : HashSet<'T> = HashSet<'T> () + + /// The set containing the given element. + /// The value for the set to contain. + /// The set containing value. + [] + let singleton (value: 'T) : HashSet<'T> = + let set = + #if FABLE_COMPILER + HashSet<'T> () + #else + HashSet<'T> 1 + #endif + set.Add value |> ignore + set + + /// Computes the union of the two sets. + /// The first input set. + /// The second input set. + /// The union of set1 and set2. + [] + let union (source1: HashSet<'T>) (source2: HashSet<'T>) : HashSet<'T> = + raiseIfNull (nameof source1) source1 + raiseIfNull (nameof source2) source2 + let union = + #if FABLE_COMPILER + HashSet<'T> () + #else + HashSet<'T> (max source1.Count source2.Count) + #endif + for item in source1 do union.Add item |> ignore + for item in source2 do union.Add item |> ignore + union + + /// Returns a new collection containing the results of applying the + /// given function to each element of the input set. + /// The function to transform elements of the input set. + /// The input set. + /// A set containing the transformed elements. + [] + let map (mapping: 'T -> 'U) (source: HashSet<'T>) : HashSet<'U> = + raiseIfNull (nameof source) source + let result = empty<'U> + for item in source do + result.Add (mapping item) |> ignore + result diff --git a/src/FSharpPlus/FSharpPlus.fsproj b/src/FSharpPlus/FSharpPlus.fsproj index b4b8374b4..cea6e0073 100644 --- a/src/FSharpPlus/FSharpPlus.fsproj +++ b/src/FSharpPlus/FSharpPlus.fsproj @@ -50,6 +50,7 @@ + diff --git a/tests/FSharpPlus.Tests/Collections.fs b/tests/FSharpPlus.Tests/Collections.fs index 297418e1e..8f985cd81 100644 --- a/tests/FSharpPlus.Tests/Collections.fs +++ b/tests/FSharpPlus.Tests/Collections.fs @@ -69,6 +69,8 @@ module Collections = let ls2:_ list = ofSeq (seq [(1, "One", '1'); (2, "Two", '2')]) let st1:_ Set = ofSeq {'1'..'3'} let st2:_ Set = ofSeq (seq [(1, "One", '1'); (2, "Two", '2')]) + let hs1:_ HashSet = ofSeq {'1'..'3'} + let hs2:_ HashSet = ofSeq (seq [(1, "One", '1'); (2, "Two", '2')]) let ss: Generic.SortedSet<_> = ofSeq (seq [3..6]) let ra: Generic.List<_> = ofSeq (seq [1..3]) let sl: Generic.SortedList<_,_> = ofSeq (seq [(1, "One"); (2, "Two")]) // but it will come back as ... @@ -118,6 +120,8 @@ module Collections = let _ls2' = toSeq ls2 let _st1' = toSeq st1 let _st2' = toSeq st2 + let _hs1' = toSeq hs1 + let _hs2' = toSeq hs2 let _ss' = toSeq ss let _ra' = toSeq ra let _sl' = toSeq sl @@ -168,6 +172,8 @@ module Collections = let ls2:_ list = ofList ([(1, "One", '1'); (2, "Two", '2')]) let st1:_ Set = ofList ['1'..'3'] let st2:_ Set = ofList ([(1, "One", '1'); (2, "Two", '2')]) + let hs1:_ HashSet = ofList ['1'..'3'] + let hs2:_ HashSet = ofList ([(1, "One", '1'); (2, "Two", '2')]) let ss: Generic.SortedSet<_> = ofList ([3..6]) let ra: Generic.List<_> = ofList ([1..3]) let sl: Generic.SortedList<_,_> = ofList ([(1, "One"); (2, "Two")]) // but it will come back as ... @@ -218,6 +224,8 @@ module Collections = let _ls2' = toList ls2 let _st1' = toList st1 let _st2' = toList st2 + let _hs1' = toList hs1 + let _hs2' = toList hs2 let _ss' = toList ss let _ra' = toList ra let _sl' = toList sl diff --git a/tests/FSharpPlus.Tests/Extensions.fs b/tests/FSharpPlus.Tests/Extensions.fs index 1ddc7b33a..338543bdd 100644 --- a/tests/FSharpPlus.Tests/Extensions.fs +++ b/tests/FSharpPlus.Tests/Extensions.fs @@ -235,6 +235,25 @@ module Extensions = areEquivalent r1 r2 + [] + let ``HashSet.union gives the same set when joined with an empty set (identity)`` () = + let m1 = HashSet.singleton "42" + let m2 = HashSet.empty + + let r1 = HashSet.union m1 m2 + + areEquivalent r1 m1 + + [] + let ``HashSet.union returns same results independent of the order (commutative)`` () = + let m1 = HashSet [1..4] + let m2 = HashSet [3..6] + + let r1 = HashSet.union m1 m2 + let r2 = HashSet.union m2 m1 + + areEquivalent r1 r2 + [] let ``String.toCodePoints >> String.ofCodePoints should preserve the original string`` () = // some naughty strings adopted from https://github.com/minimaxir/big-list-of-naughty-strings