Skip to content

Commit 9c82dbc

Browse files
committed
Adds mapWithAdditionalDependencies
1 parent eae4a56 commit 9c82dbc

File tree

4 files changed

+113
-36
lines changed

4 files changed

+113
-36
lines changed

src/FSharp.Data.Adaptive/AdaptiveValue/AdaptiveValue.fs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,43 @@ module AVal =
399399
inner <- ValueSome (struct (va, vb, vc, res))
400400
res.GetValue token
401401

402+
403+
/// <summary>
404+
/// Calls a mapping function which creates additional dependencies to be tracked.
405+
/// </summary>
406+
/// <remarks>
407+
/// Usecase for this is when a file, such as a .fsproj file changes, it needs to be reloaded in msbuild.
408+
/// Additionally fsproj files have dependencies, such as project.assets.json, that can't be determined until loaded with msbuild
409+
/// but should be reloaded if those dependent files change.
410+
/// </remarks>
411+
let mapWithAdditionalDependencies (mapping: 'a -> 'b * seq<#IAdaptiveValue>) (value: aval<'a>) : aval<'b> =
412+
let mutable lastDeps = HashSet.empty
413+
414+
{ new AbstractVal<'b>() with
415+
member x.Compute(token: AdaptiveToken) =
416+
let input = value.GetValue token
417+
418+
// re-evaluate the mapping based on the (possibly new input)
419+
let result, deps = mapping input
420+
421+
// compute the change in the additional dependencies and adjust the graph accordingly
422+
let newDeps = HashSet.ofSeq deps
423+
424+
for op in HashSet.computeDelta lastDeps newDeps do
425+
match op with
426+
| Add(_, d) ->
427+
// the new dependency needs to be evaluated with our token, s.t. we depend on it in the future
428+
d.GetValueUntyped token |> ignore
429+
| Rem(_, d) ->
430+
// we no longer need to depend on the old dependency so we can remove ourselves from its outputs
431+
lock d.Outputs (fun () -> d.Outputs.Remove x) |> ignore
432+
433+
lastDeps <- newDeps
434+
435+
result }
436+
:> aval<_>
437+
438+
402439
/// Aval for custom computations
403440
[<Sealed>]
404441
type CustomVal<'T>(compute: AdaptiveToken -> 'T) =

src/FSharp.Data.Adaptive/AdaptiveValue/AdaptiveValue.fsi

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ module AVal =
108108
/// adaptive inputs.
109109
val map3 : mapping : ('T1 -> 'T2 -> 'T3 -> 'T4) -> value1 : aval<'T1> -> value2 : aval<'T2> -> value3 : aval<'T3> -> aval<'T4>
110110

111+
/// <summary>
112+
/// Calls a mapping function which creates additional dependencies to be tracked.
113+
/// </summary>
114+
/// <remarks>
115+
/// Usecase for this is when a file, such as a .fsproj file changes, it needs to be reloaded in msbuild.
116+
/// Additionally fsproj files have dependencies, such as project.assets.json, that can't be determined until loaded with msbuild
117+
/// but should be reloaded if those dependent files change.
118+
/// </remarks>
119+
val mapWithAdditionalDependencies : mapping :( 'T1 -> 'T2 * seq<#IAdaptiveValue>) -> value: aval<'T1> -> aval<'T2>
120+
111121
/// Returns a new adaptive value that adaptively applies the mapping function to the given
112122
/// input and adaptively depends on the resulting adaptive value.
113123
/// The resulting adaptive value will hold the latest value of the aval<_> returned by mapping.

src/Test/FSharp.Data.Adaptive.Tests/AMap.fs

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -643,44 +643,13 @@ let ``[AMap] mapA``() =
643643
res |> AMap.force |> should equal (HashMap.ofList ["A", 2; "B", 4; "C", 6])
644644

645645

646-
module AVal =
647-
648-
/// <summary>
649-
/// Calls a mapping function which creates additional dependencies to be tracked.
650-
/// </summary>
651-
let mapWithAdditionalDependenies (mapping: 'a -> 'b * #seq<#IAdaptiveValue>) (value: aval<'a>) : aval<'b> =
652-
let mutable lastDeps = HashSet.empty
653-
654-
{ new AVal.AbstractVal<'b>() with
655-
member x.Compute(token: AdaptiveToken) =
656-
let input = value.GetValue token
657-
658-
// re-evaluate the mapping based on the (possibly new input)
659-
let result, deps = mapping input
660-
661-
// compute the change in the additional dependencies and adjust the graph accordingly
662-
let newDeps = HashSet.ofSeq deps
663-
664-
for op in HashSet.computeDelta lastDeps newDeps do
665-
match op with
666-
| Add(_, d) ->
667-
// the new dependency needs to be evaluated with our token, s.t. we depend on it in the future
668-
d.GetValueUntyped token |> ignore
669-
| Rem(_, d) ->
670-
// we no longer need to depend on the old dependency so we can remove ourselves from its outputs
671-
lock d.Outputs (fun () -> d.Outputs.Remove x) |> ignore
672-
673-
lastDeps <- newDeps
674-
675-
result }
676-
:> aval<_>
677646

678647
module AMap =
679-
let mapWithAdditionalDependenies (mapping: HashMap<'K, 'T1> -> HashMap<'K, 'T2 * #seq<#IAdaptiveValue>>) (map: amap<'K, 'T1>) =
648+
let mapWithAdditionalDependencies (mapping: HashMap<'K, 'T1> -> HashMap<'K, 'T2 * #seq<#IAdaptiveValue>>) (map: amap<'K, 'T1>) =
680649
let mapping =
681650
mapping
682651
>> HashMap.map(fun _ v ->
683-
AVal.constant v |> AVal.mapWithAdditionalDependenies (id)
652+
AVal.constant v |> AVal.mapWithAdditionalDependencies (id)
684653
)
685654
AMap.batchRecalcDirty mapping map
686655

@@ -712,7 +681,7 @@ let ``[AMap] batchRecalcDirty``() =
712681
let mutable lastBatch = Unchecked.defaultof<_>
713682
let res =
714683
projs
715-
|> AMap.mapWithAdditionalDependenies(fun d ->
684+
|> AMap.mapWithAdditionalDependencies(fun d ->
716685
lastBatch <- d
717686
HashMap.ofList [
718687
for k,v in d do

src/Test/FSharp.Data.Adaptive.Tests/AVal.fs

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,6 @@ let ``[AVal] mapNonAdaptive GC correct``() =
275275
transact (fun () -> v.Value <- 100)
276276
test |> AVal.force |> should equal 101
277277

278-
279-
280278
[<Test>]
281279
let ``[AVal] multi map non-adaptive and bind``() =
282280
let v = AVal.init true
@@ -289,3 +287,66 @@ let ``[AVal] multi map non-adaptive and bind``() =
289287

290288
transact (fun () -> v.Value <- false)
291289
output |> AVal.force |> should equal 1
290+
291+
292+
293+
[<Test>]
294+
let ``[AVal] mapWithAdditionalDependencies``() =
295+
let v = cval 1
296+
let incrDep (dep : cval<_>) =
297+
dep.Value <- dep.Value + 1
298+
let mutable dependency1 = Unchecked.defaultof<_>
299+
let newDep1 () =
300+
dependency1 <- cval 2
301+
dependency1
302+
let mutable dependency2 = Unchecked.defaultof<_>
303+
let newDep2 () =
304+
dependency2 <- cval 3
305+
dependency2
306+
let mutable mappingCalls = 0
307+
let incrMapping () =
308+
mappingCalls <- mappingCalls + 1
309+
310+
let mapping (i : int) =
311+
incrMapping ()
312+
// dependencies aren't known until mapping time
313+
i * 2, [newDep1(); newDep2()]
314+
315+
let output = v |> AVal.mapWithAdditionalDependencies mapping
316+
317+
output |> AVal.force |> should equal 2
318+
mappingCalls |> should equal 1
319+
320+
transact (fun () -> v.Value <- 2)
321+
output |> AVal.force |> should equal 4
322+
mappingCalls |> should equal 2
323+
324+
transact (fun () -> incrDep dependency1)
325+
output |> AVal.force |> should equal 4
326+
mappingCalls |> should equal 3
327+
328+
329+
transact (fun () -> incrDep dependency1)
330+
output |> AVal.force |> should equal 4
331+
mappingCalls |> should equal 4
332+
333+
transact (fun () -> v.Value <- 2)
334+
output |> AVal.force |> should equal 4
335+
mappingCalls |> should equal 4
336+
337+
338+
transact (fun () -> v.Value <- 1)
339+
output |> AVal.force |> should equal 2
340+
mappingCalls |> should equal 5
341+
342+
transact (fun () ->
343+
v.Value <- 1
344+
incrDep dependency1)
345+
output |> AVal.force |> should equal 2
346+
mappingCalls |> should equal 6
347+
348+
transact (fun () ->
349+
incrDep dependency2
350+
incrDep dependency1)
351+
output |> AVal.force |> should equal 2
352+
mappingCalls |> should equal 7

0 commit comments

Comments
 (0)