Skip to content

Commit 09675a2

Browse files
authored
Limited applicative support for dictionaries (#575)
1 parent 9cb4063 commit 09675a2

File tree

5 files changed

+54
-0
lines changed

5 files changed

+54
-0
lines changed

src/FSharpPlus/Control/Applicative.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ type Lift2 =
124124
static member Lift2 (f, (x: Choice<'T,'Error> , y: Choice<'U,'Error> ), _mthd: Lift2) = Choice.map2 f x y
125125
static member Lift2 (f, (x: Map<'Key,'T> , y : Map<'Key,'U> ), _mthd: Lift2) = Map.mapValues2 f x y
126126
static member Lift2 (f, (x: Dictionary<'Key,'T>, y: Dictionary<'Key,'U>), _mthd: Lift2) = Dictionary.map2 f x y
127+
static member Lift2 (f, (x: IDictionary<'Key,'T>, y: IDictionary<'Key,'U>), _mthd: Lift2) = Dict.map2 f x y
128+
static member Lift2 (f, (x: IReadOnlyDictionary<'Key,'T>, y: IReadOnlyDictionary<'Key,'U>), _mthd: Lift2) = IReadOnlyDictionary.map2 f x y
127129
#if !FABLE_COMPILER
128130
static member Lift2 (f, (x: Expr<'T> , y: Expr<'U> ), _mthd: Lift2) = <@ f %x %y @>
129131
#endif
@@ -171,6 +173,8 @@ type Lift3 =
171173
static member Lift3 (f, (x: Choice<'T,'Error> , y: Choice<'U,'Error> , z: Choice<'V, 'Error> ), _mthd: Lift3) = Choice.map3 f x y z
172174
static member Lift3 (f, (x: Map<'Key,'T> , y: Map<'Key,'U> , z: Map<'Key, 'V> ), _mthd: Lift3) = Map.mapValues3 f x y z
173175
static member Lift3 (f, (x: Dictionary<'Key,'T>, y: Dictionary<'Key,'U>, z: Dictionary<'Key, 'V>), _mthd: Lift3) = Dictionary.map3 f x y z
176+
static member Lift3 (f, (x: IDictionary<'Key,'T>, y: IDictionary<'Key,'U>, z: IDictionary<'Key, 'V>), _mthd: Lift3) = Dict.map3 f x y z
177+
static member Lift3 (f, (x: IReadOnlyDictionary<'Key,'T>, y: IReadOnlyDictionary<'Key,'U>, z: IReadOnlyDictionary<'Key, 'V>), _mthd: Lift3) = IReadOnlyDictionary.map3 f x y z
174178
#if !FABLE_COMPILER
175179
static member Lift3 (f, (x: Expr<'T> , y: Expr<'U> , z: Expr<'V> ), _mthd: Lift3) = <@ f %x %y %z @>
176180
#endif

src/FSharpPlus/Extensions/Dict.fs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,23 @@ module Dict =
7373
| None -> ()
7474
dct :> IDictionary<'Key, 'U>
7575

76+
/// <summary>Combines values from three dictionaries using mapping function.</summary>
77+
/// <remarks>Keys that are not present on every dictionary are dropped.</remarks>
78+
/// <param name="mapping">The mapping function.</param>
79+
/// <param name="source1">First input dictionary.</param>
80+
/// <param name="source2">Second input dictionary.</param>
81+
/// <param name="source3">Third input dictionary.</param>
82+
///
83+
/// <returns>The mapped dictionary.</returns>
84+
let map3 mapping (source1: IDictionary<'Key, 'T1>) (source2: IDictionary<'Key, 'T2>) (source3: IDictionary<'Key, 'T3>) =
85+
let dct = Dictionary<'Key, 'U> ()
86+
let f = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt mapping
87+
for KeyValue(k, vx) in source1 do
88+
match tryGetValue k source2, tryGetValue k source3 with
89+
| Some vy, Some vz -> dct.Add (k, f.Invoke (vx, vy, vz))
90+
| _ , _ -> ()
91+
dct :> IDictionary<'Key, 'U>
92+
7693
/// <summary>Applies given function to each value of the given dictionary.</summary>
7794
/// <param name="chooser">The mapping function.</param>
7895
/// <param name="source">The input dictionary.</param>

src/FSharpPlus/Extensions/IReadOnlyDictionary.fs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,24 @@ module IReadOnlyDictionary =
8080
| None -> ()
8181
dct :> IReadOnlyDictionary<'Key, 'U>
8282

83+
/// <summary>Combines values from three read-only dictionaries using mapping function.</summary>
84+
/// <remarks>Keys that are not present on every dictionary are dropped.</remarks>
85+
/// <param name="mapping">The mapping function.</param>
86+
/// <param name="source1">First input dictionary.</param>
87+
/// <param name="source2">Second input dictionary.</param>
88+
/// <param name="source3">Third input dictionary.</param>
89+
///
90+
/// <returns>The mapped IReadOnlyDictionary.</returns>
91+
let map3 mapping (source1: IReadOnlyDictionary<'Key, 'T1>) (source2: IReadOnlyDictionary<'Key, 'T2>) (source3: IReadOnlyDictionary<'Key, 'T3>) =
92+
let dct = Dictionary<'Key, 'U> ()
93+
let f = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt mapping
94+
for KeyValue(k, vx) in source1 do
95+
match tryGetValue k source2, tryGetValue k source3 with
96+
| Some vy, Some vz -> dct.Add (k, f.Invoke (vx, vy, vz))
97+
| _ , _ -> ()
98+
dct :> IReadOnlyDictionary<'Key, 'U>
99+
100+
83101
/// <summary>Maps the given function over each key and value in the read-only dictionary.</summary>
84102
/// <param name="mapper">The mapping function.</param>
85103
/// <param name="source">The input IReadOnlyDictionary.</param>

tests/FSharpPlus.Tests/ComputationExpressions.fs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ module ComputationExpressions =
1313

1414
let task<'t> = monad'<Task<'t>>
1515

16+
[<Test>]
17+
let oneLayerApplicativeWithoutReturn () =
18+
// dictionaries don't support Return
19+
let testVal14 = applicative {
20+
let! x1 = dict [1,1]
21+
and! x2 = dict [1,1]
22+
and! x3 = dict [1,1]
23+
and! x4 = dict [1,1]
24+
return x1 + x2 + x3 + x4 }
25+
CollectionAssert.AreEqual (dict [1, 4], testVal14)
26+
1627
[<Test>]
1728
let twoLayersApplicatives () =
1829
let id : Task<Validation<_, string>> = Failure (Map.ofList ["Id", ["Negative number"]]) |> Task.FromResult

tests/FSharpPlus.Tests/General.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,10 @@ module Applicative =
12241224
Assert.IsInstanceOf<Option<WrappedSeqF<int>>> (Some res5)
12251225
CollectionAssert.AreEqual (WrappedSeqF [5], res5)
12261226

1227+
let testVal11 = (+) "h" <!> dict [1, "i"; 2, "ello"]
1228+
CollectionAssert.AreEqual (dict [(1, "hi"); (2, "hello")], testVal11)
1229+
1230+
[<Test>]
12271231
let testLift2 () =
12281232
let expectedEffects = ["Using WrappedSeqD's Return"; "Using WrappedSeqD's Apply"; "Using WrappedSeqD's Apply"]
12291233
SideEffects.reset ()

0 commit comments

Comments
 (0)