Skip to content

Commit a5281cc

Browse files
authored
+ zip3 (#673)
1 parent 0d3f93c commit a5281cc

File tree

6 files changed

+178
-59
lines changed

6 files changed

+178
-59
lines changed

src/FSharpPlus/Control/Functor.fs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,6 @@ type Zip =
222222
static member Zip ((x: Async<'T> , y: Async<'U> , _output: Async<'T*'U> ), _mthd: Zip) = Async.zip x y
223223
#if !FABLE_COMPILER
224224
static member Zip ((x: Task<'T> , y: Task<'U> , _output: Task<'T*'U> ), _mthd: Zip) = Task.zip x y
225-
#endif
226-
#if !FABLE_COMPILER
227225
static member Zip ((x: ValueTask<'T> , y: ValueTask<'U> , _output: ValueTask<'T*'U> ), _mthd: Zip) = ValueTask.zip x y
228226
#endif
229227

@@ -239,6 +237,43 @@ type Zip with
239237
static member inline Zip ((_: ^t when ^t : null and ^t: struct, _: ^u when ^u : null and ^u: struct, _output: ^r when ^r : null and ^r: struct), _mthd: Default1) = id
240238
static member inline Zip ((x: '``ZipFunctor<'T1>`` , y: '``ZipFunctor<'T2>`` , _output: '``ZipFunctor<'T1 * 'T2>`` ), _mthd: Default1) = Zip.InvokeOnInstance x y : '``ZipFunctor<'T1 * 'T2>``
241239

240+
241+
type Zip3 =
242+
inherit Default1
243+
static member Zip3 ((x: IEnumerator<'T1> , y: IEnumerator<'T2> , z: IEnumerator<'T3> , _output: IEnumerator<'T1 * 'T2 * 'T3> ), _mthd: Zip3) = Enumerator.zip3 x y z
244+
static member Zip3 ((x: seq<'T> , y: seq<'U> , z: seq<'V> , _output: seq<'T*'U*'V> ), _mthd: Zip3) = Seq.zip3 x y z
245+
static member Zip3 ((x: IDictionary<'K, 'T> , y: IDictionary<'K, 'U> , z: IDictionary<'K, 'V> , _output: IDictionary<'K, 'T * 'U * 'V> ), _mthd: Zip3) = Dict.zip3 x y z
246+
static member Zip3 ((x: IReadOnlyDictionary<'K, 'T>, y: IReadOnlyDictionary<'K, 'U>, z: IReadOnlyDictionary<'K, 'V>, _output: IReadOnlyDictionary<'K, 'T * 'U * 'V>), _mthd: Zip3) = IReadOnlyDictionary.zip3 x y z
247+
static member Zip3 ((x: Dictionary<'K, 'T> , y: Dictionary<'K, 'U> , z: Dictionary<'K, 'V> , _output: Dictionary<'K, 'T * 'U * 'V> ), _mthd: Zip3) = Dictionary.zip3 x y z
248+
static member Zip3 ((x: Map<'K, 'T> , y: Map<'K, 'U> , z: Map<'K, 'V> , _output: Map<'K, 'T * 'U * 'V> ), _mthd: Zip3) = Map.zip3 x y z
249+
static member Zip3 ((f: 'R -> 'T1 , g: 'R -> 'T2 , h: 'R -> 'T3 , _output: 'R -> 'T1 * 'T2 * 'T3 ), _mthd: Zip3) = fun r -> (f r, g r, h r)
250+
static member Zip3 ((f: Func<'R, 'T1> , g: Func<'R, 'T2> , h: Func<'R, 'T3> , _output: Func<'R, 'T1 * 'T2 * 'T3> ), _mthd: Zip3) = Func<_,_> (fun r -> (f.Invoke r, g.Invoke r, h.Invoke r))
251+
static member Zip3 ((x: list<'T1> , y: list<'T2> , z: list<'T3> , _output: list<'T1 * 'T2 * 'T3> ), _mthd: Zip3) = List.zip3Shortest x y z
252+
static member Zip3 ((x: 'T1 [] , y: 'T2 [] , z: 'T3 [] , _output: ('T1 * 'T2 * 'T3) [] ), _mthd: Zip3) = Array.zip3Shortest x y z
253+
static member Zip3 ((x: ResizeArray<'T1> , y: ResizeArray<'T2> , z: ResizeArray<'T3> , _output: ResizeArray<'T1 * 'T2 * 'T3> ), _mthd: Zip3) = ResizeArray.zip3Shortest x y z
254+
static member Zip3 ((x: option<'T1> , y: option<'T2> , z: option<'T3> , _output: option<'T1 * 'T2 * 'T3> ), _mthd: Zip3) = Option.zip3 x y z
255+
static member Zip3 ((x: voption<'T1> , y: voption<'T2> , z: voption<'T3> , _output: voption<'T1 * 'T2 * 'T3> ), _mthd: Zip3) = ValueOption.zip3 x y z
256+
static member inline Zip3 ((x: Result<'T, 'Error> , y: Result<'U, 'Error> , z: Result<'V, 'Error> , _output: Result<'T * 'U * 'V, 'Error> ), _mthd: Zip3) = Result.apply3With Plus.Invoke (fun a b c -> a, b, c) x y z
257+
static member inline Zip3 ((x: Choice<'T, 'Error> , y: Choice<'U, 'Error> , z: Choice<'V, 'Error> , _output: Choice<'T * 'U * 'V, 'Error> ), _mthd: Zip3) = Choice.apply3With Plus.Invoke (fun a b c -> a, b, c) x y z
258+
static member Zip3 ((x: Async<'T1> , y: Async<'T2> , z: Async<'T3> , _output: Async<'T1 * 'T2 * 'T3> ), _mthd: Zip3) = Async.zip3 x y z
259+
#if !FABLE_COMPILER
260+
static member Zip3 ((x: Task<'T1> , y: Task<'T2> , z: Task<'T3> , _output: Task<'T1 * 'T2 * 'T3> ), _mthd: Zip3) = Task.zip3 x y z
261+
static member Zip3 ((x: ValueTask<'T1> , y: ValueTask<'T2> , z: ValueTask<'T3> , _output: ValueTask<'T1 * 'T2 * 'T3> ), _mthd: Zip3) = ValueTask.zip3 x y z
262+
#endif
263+
264+
static member inline Invoke (source1: '``ZipFunctor<'T1>``) (source2: '``ZipFunctor<'T2>``) (source3: '``ZipFunctor<'T3>``) =
265+
let inline call_5 (a: ^a, b: ^b, c: ^c, d: ^d, e: ^e) = ((^a or ^b or ^c or ^d or ^e) : (static member Zip3 : (_*_*_*_)*_ -> _) (b, c, d, e), a)
266+
let inline call (a: 'a, b: 'b, c: 'c, d: 'd) = call_5 (a, b, c, d, Unchecked.defaultof<'r>) : 'r
267+
call (Unchecked.defaultof<Zip3>, source1, source2, source3) : '``ZipFunctor<'T1 * 'T2 * 'T3>``
268+
269+
static member inline InvokeOnInstance (source1: '``ZipFunctor<'T1>``) (source2: '``ZipFunctor<'T2>``) (source3: '``ZipFunctor<'T3>``) : '``ZipFunctor<'T1 * 'T2 * 'T3>`` =
270+
((^``ZipFunctor<'T1>`` or ^``ZipFunctor<'T2>`` or ^``ZipFunctor<'T3>`` or ^``ZipFunctor<'T1 * 'T2 * 'T3>``) : (static member Zip3 : _*_*_ -> _) source1, source2, source3)
271+
272+
type Zip3 with
273+
static member inline Zip3 ((_: ^t when ^t : null and ^t: struct, _: ^u when ^u : null and ^u: struct, _: ^v when ^v : null and ^v: struct, _output: ^r when ^r : null and ^r: struct), _mthd: Default1) = id
274+
static member inline Zip3 ((x: '``ZipFunctor<'T1>`` , y: '``ZipFunctor<'T2>`` , z: '``ZipFunctor<'T3>`` , _output: '``ZipFunctor<'T1 * 'T2 * 'T3>``), _mthd: Default1) = Zip3.InvokeOnInstance x y z : '``ZipFunctor<'T1 * 'T2 * 'T3>``
275+
276+
242277
#endif
243278
#if !FABLE_COMPILER
244279

src/FSharpPlus/Data/NonEmptyList.fs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,16 @@ module NonEmptyList =
147147
/// <returns>List with corresponding pairs of input lists.</returns>
148148
let zipShortest (list1: NonEmptyList<'T>) (list2: NonEmptyList<'U>) =
149149
{ Head = (list1.Head, list2.Head); Tail = List.zipShortest list1.Tail list2.Tail }
150+
151+
/// <summary>
152+
/// Zip safely three lists. If one list is shorter, excess elements are discarded from the right end of the longer list.
153+
/// </summary>
154+
/// <param name="list1">First input list.</param>
155+
/// <param name="list2">Second input list.</param>
156+
/// <param name="list3">Third input list.</param>
157+
/// <returns>List with corresponding triplets of input lists.</returns>
158+
let zip3Shortest (list1: NonEmptyList<'T1>) (list2: NonEmptyList<'T2>) (list3: NonEmptyList<'T3>) =
159+
{ Head = (list1.Head, list2.Head, list3.Head); Tail = List.zip3Shortest list1.Tail list2.Tail list3.Tail }
150160

151161
/// <summary>Adds an element to the beginning of the given list</summary>
152162
/// <param name="value">The element to add</param>
@@ -1013,6 +1023,9 @@ type NonEmptyList<'t> with
10131023

10141024
[<EditorBrowsable(EditorBrowsableState.Never)>]
10151025
static member Zip (x, y) = NonEmptyList.zipShortest x y
1026+
1027+
[<EditorBrowsable(EditorBrowsableState.Never)>]
1028+
static member Zip3 (x, y, z) = NonEmptyList.zip3Shortest x y z
10161029

10171030
static member (>>=) ({Head = x; Tail = xs}, f: _->NonEmptyList<'b>) =
10181031
let {Head = y; Tail = ys} = f x

src/FSharpPlus/Operators.fs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,15 @@ module Operators =
187187
/// <category index="1">Functor</category>
188188
let inline zip (source1: '``ZipFunctor<'T1>``) (source2: '``ZipFunctor<'T2>``) : '``ZipFunctor<'T1 * 'T2>`` = Zip.Invoke source1 source2
189189

190+
/// <summary>
191+
/// Zips (tuple) three functors.
192+
/// </summary>
193+
/// <remarks>
194+
/// For collections, if one collection is shorter, excess elements are discarded from the right end of the longer collection.
195+
/// </remarks>
196+
/// <category index="1">Functor</category>
197+
let inline zip3 (source1: '``ZipFunctor<'T1>``) (source2: '``ZipFunctor<'T2>``) (source3: '``ZipFunctor<'T3>``) : '``ZipFunctor<'T1 * 'T2 * 'T3>`` = Zip3.Invoke source1 source2 source3
198+
190199
// Applicative ------------------------------------------------------------
191200

192201
/// <summary>

tests/FSharpPlus.Tests/Applicatives.fs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
open System
44
open System.Collections.ObjectModel
5+
open System.Threading.Tasks
56
open FSharpPlus
67
open FSharpPlus.Data
78
open NUnit.Framework
@@ -33,3 +34,59 @@ module Applicatives =
3334

3435
let arr3 = (+) <!> Compose (async { return [|1;2;3|] } ) <.> Compose (async { return [|10;20;30|] })
3536
CollectionAssert.AreEqual ([|11; 22; 33|], arr3 |> Compose.run |> Async.RunSynchronously)
37+
38+
39+
[<Test>]
40+
let zip3Test () =
41+
42+
SideEffects.reset ()
43+
let _a = zip3 (seq [1;2;3]) (seq [1. .. 3. ]) (seq ['a';'b';'c'])
44+
Assert.AreEqual (list<string>.Empty, SideEffects.get ())
45+
46+
let _b = zip3 (dict [1,'1' ; 2,'2' ; 4,'4']) (dict [1,'1' ; 2,'2' ; 3,'3']) (dict [1,'a' ; 2,'b' ; 3,'c'])
47+
let _c = zip3 [ 1;2;3 ] [ 1. .. 3. ] ['a';'b';'c']
48+
let _d = zip3 [|1;2;3|] [|1. .. 3.|] [|'a';'b';'c'|]
49+
let _e = zip3 (async {return 1}) (async {return '2'}) (async {return 3.})
50+
let _f = zip3 (Task.FromResult 1) (Task.FromResult '2') (Task.FromResult 3.)
51+
let _g = zip3 (Some 1) (Some '2') (Some 3.)
52+
let _h = zip3 (Ok 1) (if true then Ok '2' else Error "No") (Ok 3.)
53+
54+
let _fa a = zip3 a (seq [1. .. 3. ]) (seq ['a';'b';'c'])
55+
let _fb a = zip3 a [ 1. .. 3. ] ['a';'b';'c']
56+
let _fc a = zip3 a [|1. .. 3.|] [|'a';'b';'c'|]
57+
let _fd a = zip3 a (async {return '2'}) (async {return 3.})
58+
let _fe a = zip3 a (Task.FromResult '2') (Task.FromResult 3.)
59+
60+
let _ga b = zip3 (seq [1;2;3]) b (seq ['a';'b';'c'])
61+
let _gb b = zip3 [ 1;2;3 ] b ['a';'b';'c']
62+
let _gc b = zip3 [|1;2;3|] b [|'a';'b';'c'|]
63+
let _gd b = zip3 (async {return 1}) b (async {return 3.})
64+
let _ge b = zip3 (Task.FromResult 1) b (Task.FromResult 3.)
65+
66+
let _ha c = zip3 (seq [1;2;3]) (seq [1. .. 3. ]) c
67+
let _hb c = zip3 [ 1;2;3 ] [ 1. .. 3. ] c
68+
let _hc c = zip3 [|1;2;3|] [|1. .. 3.|] c
69+
let _hd c = zip3 (async {return 1}) (async {return '2'}) c
70+
let _he c = zip3 (Task.FromResult 1) (Task.FromResult '2') c
71+
72+
let _ia : _ -> _ -> _ -> _ seq = zip3
73+
let _ib : _ -> _ -> _ -> _ list = zip3
74+
let _ic : _ -> _ -> _ -> _ [] = zip3
75+
let _id : _ -> _ -> _ -> Async<_> = zip3
76+
let _ie : _ -> _ -> _ -> Task<_> = zip3
77+
78+
()
79+
80+
[<Test>]
81+
let genericZip3Shortest () =
82+
let a = zip3 [|1; 2; 3|] [|"a"; "b"|] [|10.; 20.; 30.|]
83+
CollectionAssert.AreEqual ([|1,"a",10.; 2,"b",20.|], a)
84+
85+
let l = zip3 [1; 2] ["a"; "b"; "c"] [10.; 20.; 30.]
86+
CollectionAssert.AreEqual ([1,"a",10.; 2,"b",20.], l)
87+
88+
let e = zip3 (ResizeArray [1; 2]) (ResizeArray ["a"; "b"; "c"]) (ResizeArray [10.; 20.])
89+
CollectionAssert.AreEqual (ResizeArray [1,"a",10.; 2,"b",20.], e)
90+
91+
let nel = zip3 (NonEmptyList.ofList [1; 2]) (NonEmptyList.ofList ["a"; "b"; "c"]) (NonEmptyList.ofList [10.; 20.; 30.])
92+
CollectionAssert.AreEqual (NonEmptyList.ofList [1,"a",10.; 2,"b",20.], nel)

tests/FSharpPlus.Tests/General.fs

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -67,62 +67,6 @@ type WrappedListC<'s> = WrappedListC of 's list with
6767
static member Zero = WrappedListC List.empty
6868
static member Sum (lst: seq<WrappedListC<_>>) = Seq.head lst
6969

70-
type WrappedListD<'s> = WrappedListD of 's list with
71-
interface Collections.Generic.IEnumerable<'s> with member x.GetEnumerator () = (let (WrappedListD x) = x in x :> _ seq).GetEnumerator ()
72-
interface Collections.IEnumerable with member x.GetEnumerator () = (let (WrappedListD x) = x in x :> _ seq).GetEnumerator () :> Collections.IEnumerator
73-
static member Return (x) = SideEffects.add "Using WrappedListD's Return"; WrappedListD [x]
74-
static member (>>=) ((WrappedListD x):WrappedListD<'T>, f) = SideEffects.add "Using WrappedListD's Bind"; WrappedListD (List.collect (f >> (fun (WrappedListD x) -> x)) x)
75-
static member inline FoldMap (WrappedListD x, f) =
76-
SideEffects.add "Using optimized foldMap"
77-
Seq.fold (fun x y -> x ++ (f y)) zero x
78-
static member Zip (WrappedListD x, WrappedListD y) = SideEffects.add "Using WrappedListD's zip"; WrappedListD (List.zip x y)
79-
static member Exists (x, f) =
80-
SideEffects.add "Using WrappedListD's Exists"
81-
let (WrappedListD lst) = x
82-
List.exists f lst
83-
static member Pick (x, f) =
84-
SideEffects.add "Using WrappedListD's Pick"
85-
let (WrappedListD lst) = x
86-
List.pick f lst
87-
static member Min x =
88-
SideEffects.add "Using WrappedListD's Min"
89-
let (WrappedListD lst) = x
90-
List.min lst
91-
static member MaxBy (x, f) =
92-
SideEffects.add "Using WrappedListD's MaxBy"
93-
let (WrappedListD lst) = x
94-
List.maxBy f lst
95-
static member MapIndexed (WrappedListD x, f) =
96-
SideEffects.add "Using WrappedListD's MapIndexed"
97-
WrappedListD (List.mapi f x)
98-
static member ChooseIndexed (WrappedListD x, f) =
99-
SideEffects.add "Using WrappedListD's ChooseIndexed"
100-
WrappedListD (List.choosei f x)
101-
static member Lift3 (f, WrappedListD x, WrappedListD y, WrappedListD z) =
102-
SideEffects.add "Using WrappedListD's Lift3"
103-
WrappedListD (List.lift3 f x y z)
104-
static member IterateIndexed (WrappedListD x, f) =
105-
SideEffects.add "Using WrappedListD's IterateIndexed"
106-
List.iteri f x
107-
static member inline FoldIndexed (WrappedListD x, f, z) =
108-
SideEffects.add "Using WrappedListD's FoldIndexed"
109-
foldi f z x
110-
static member inline TraverseIndexed (WrappedListD x, f) =
111-
SideEffects.add "Using WrappedListD's TraverseIndexed"
112-
WrappedListD <!> (traversei f x : ^r)
113-
static member FindIndex (WrappedListD x, y) =
114-
SideEffects.add "Using WrappedListD's FindIndex"
115-
findIndex y x
116-
static member FindSliceIndex (WrappedListD x, WrappedListD y) =
117-
SideEffects.add "Using WrappedListD's FindSliceIndex"
118-
findSliceIndex y x
119-
static member FindLastSliceIndex (WrappedListD x, WrappedListD y) =
120-
SideEffects.add "Using WrappedListD's FindLastSliceIndex"
121-
findLastSliceIndex y x
122-
member this.Length =
123-
SideEffects.add "Using WrappedListD's Length"
124-
let (WrappedListD lst) = this
125-
List.length lst
12670
type WrappedListE<'s> = WrappedListE of 's list with
12771
static member Return x = WrappedListE [x]
12872
static member (>>=) (WrappedListE x: WrappedListE<'T>, f) = WrappedListE (List.collect (f >> (fun (WrappedListE x) -> x)) x)

tests/FSharpPlus.Tests/Helpers.fs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
module FSharpPlus.Tests.Helpers
22

3-
open NUnit.Framework
3+
open System
44
open System.Collections
5+
open NUnit.Framework
6+
open FSharpPlus
7+
open FSharpPlus.Control
58

69
let areEqual (x:'t) (y:'t) = Assert.AreEqual (x, y)
710
let areStEqual x y = Assert.IsTrue( (x = y), sprintf "Expected %A to be structurally equal to %A" x y)
@@ -14,3 +17,61 @@ module SideEffects =
1417
let add x = effects.Add (x)
1518
let get () = effects |> Seq.toList
1619
let are lst = areEquivalent lst (get ())
20+
21+
22+
type WrappedListD<'s> = WrappedListD of 's list with
23+
interface Collections.Generic.IEnumerable<'s> with member x.GetEnumerator () = (let (WrappedListD x) = x in x :> _ seq).GetEnumerator ()
24+
interface Collections.IEnumerable with member x.GetEnumerator () = (let (WrappedListD x) = x in x :> _ seq).GetEnumerator () :> Collections.IEnumerator
25+
static member Return (x) = SideEffects.add "Using WrappedListD's Return"; WrappedListD [x]
26+
static member (>>=) ((WrappedListD x):WrappedListD<'T>, f) = SideEffects.add "Using WrappedListD's Bind"; WrappedListD (List.collect (f >> (fun (WrappedListD x) -> x)) x)
27+
static member inline FoldMap (WrappedListD x, f) =
28+
SideEffects.add "Using optimized foldMap"
29+
Seq.fold (fun x y -> x ++ (f y)) zero x
30+
static member Zip (WrappedListD x, WrappedListD y) = SideEffects.add "Using WrappedListD's zip"; WrappedListD (List.zip x y)
31+
static member Exists (x, f) =
32+
SideEffects.add "Using WrappedListD's Exists"
33+
let (WrappedListD lst) = x
34+
List.exists f lst
35+
static member Pick (x, f) =
36+
SideEffects.add "Using WrappedListD's Pick"
37+
let (WrappedListD lst) = x
38+
List.pick f lst
39+
static member Min x =
40+
SideEffects.add "Using WrappedListD's Min"
41+
let (WrappedListD lst) = x
42+
List.min lst
43+
static member MaxBy (x, f) =
44+
SideEffects.add "Using WrappedListD's MaxBy"
45+
let (WrappedListD lst) = x
46+
List.maxBy f lst
47+
static member MapIndexed (WrappedListD x, f) =
48+
SideEffects.add "Using WrappedListD's MapIndexed"
49+
WrappedListD (List.mapi f x)
50+
static member ChooseIndexed (WrappedListD x, f) =
51+
SideEffects.add "Using WrappedListD's ChooseIndexed"
52+
WrappedListD (List.choosei f x)
53+
static member Lift3 (f, WrappedListD x, WrappedListD y, WrappedListD z) =
54+
SideEffects.add "Using WrappedListD's Lift3"
55+
WrappedListD (List.lift3 f x y z)
56+
static member IterateIndexed (WrappedListD x, f) =
57+
SideEffects.add "Using WrappedListD's IterateIndexed"
58+
List.iteri f x
59+
static member inline FoldIndexed (WrappedListD x, f, z) =
60+
SideEffects.add "Using WrappedListD's FoldIndexed"
61+
foldi f z x
62+
static member inline TraverseIndexed (WrappedListD x, f) =
63+
SideEffects.add "Using WrappedListD's TraverseIndexed"
64+
WrappedListD <!> (traversei f x : ^r)
65+
static member FindIndex (WrappedListD x, y) =
66+
SideEffects.add "Using WrappedListD's FindIndex"
67+
findIndex y x
68+
static member FindSliceIndex (WrappedListD x, WrappedListD y) =
69+
SideEffects.add "Using WrappedListD's FindSliceIndex"
70+
findSliceIndex y x
71+
static member FindLastSliceIndex (WrappedListD x, WrappedListD y) =
72+
SideEffects.add "Using WrappedListD's FindLastSliceIndex"
73+
findLastSliceIndex y x
74+
member this.Length =
75+
SideEffects.add "Using WrappedListD's Length"
76+
let (WrappedListD lst) = this
77+
List.length lst

0 commit comments

Comments
 (0)