Giter Club home page Giter Club logo

fsharp.data.adaptive's Introduction

DotNet Fable NuGet NuGet Discord

FSharp.Data.Adaptive

FSharp.Data.Adaptive provides a clean API for handling changeable ("adaptive") data while sticking to functional principles. It allows programmers to treat adaptive values just like immutable data while still maintaining efficient updates. Many adaptive data structures and operators are provided.

Purpose

Writing a program that adjusts its output according to changes in data or user input is traditionally either:

  1. very tedious work, error prone and involves lots of caches with unknown lifetime, cluttered code handling reuse of values, etc.; or
  2. rather inefficient when employing functional principles (immutable data). The program state is often effectively replaced on every change and (at least conceptually) the entire program is re-executed.

FSharp.Data.Adaptive tackles this with a somewhat in-between solution. It employs a functional-first approach on the surface and abstracts away caching/reuse issues in a clean way underneath.

Overview

FSharp.Data.Adaptive provides several container types that are used to represent adaptive values and various combinators for operating on these containers.

aval<'T> holds a single changeable value. We distinguish two associated variants of each container cell type:

  1. cval<'T>: Changeable cells that can be modified directly by user code . These are prefixed with c for "changeable".
  2. aval<'T>: Adaptive cells that represent dependent computations. These depend on changeable cells or other adaptive cells, but cannot be directly modified by user code. They are prefixed with a for "adaptive".

Changeable cells are compatible (via inheritance) with their adaptive counterparts.

let changeable = cval 10
let dependent = changeable |> AVal.map (fun a -> 2 * a)

dependent |> AVal.force // => 20
transact (fun () -> changeable.Value <- 1)
dependent |> AVal.force // => 2

transact manually changes the value of a changable cell. force recovers the current value of an adaptive cell, thus leaving adaptive world.

Adaptive combinators are efficient. The ones provided for sets aset<'T>, lists alist<'T> or maps amap<'Key,'Value> recompute results incrementally.

let input = clist [1;2;3]
let dependent = 
    input 
    |> AList.map (fun v -> v * v)
    |> AList.fold (+) 0

dependent |> AVal.force // => 14
transact (fun () -> input.Append 4)
dependent |> AVal.force // => 30

The cost of updates stays constant regardless of the length of the list.

Adaptive depencies are dynamic. A dependency may or may not exist depending on another dependency.

let a = cval "some dependency"
let b = cval "other input"
let param = cval 0.5

let result = 
    param 
    |> AVal.bind (fun p -> 
        if p <= 0.33 then a :> aval<_>
        elif p <= 0.66 then b :> aval<_>
        else AVal.constant "invalid"
    )

Thanks to bind, dependencies between cells only exist if the condition is met. No recomputations are ever performed as long as the result doesn't demand it via dependencies.

Contribute

Communicate via:

Discord Discord

Github issues

about things like bugs, feature requests, use cases or your own implementations of adaptive data structures. We're looking forward to having a conversation!

Documentation

Find the FSharp.Data.Adaptive technical documentation here. It contains a full list of adaptive data structures and operators as well as descriptions of their implementations.

Projects and Resources

History

The project started back in 2013 at the VRVis Research Center and was mainly developed for the Aardvark.Platform. Over time it became more and more apparent that adaptive data has the potential to benefit many different applications, so we decided to move it to this standalone library (outside the Aardvark world). If you're interested in the development history, the last stable aardvark-implementation (and most of its history) can be found in Aardvark.Base.Incremental.

fsharp.data.adaptive's People

Contributors

adelarsq avatar aszabo314 avatar delneg avatar dsyme avatar haraldsteinlechner avatar krauthaufen avatar luithefirst avatar ncave avatar nojaf avatar pkese avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fsharp.data.adaptive's Issues

AList - missing pairwise

I don't see function AList.pairwise, is it called something else?

If it is not there can it be added?

add ASet.custom to C# interface

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
static member Custom (f : Func<AdaptiveToken, Func<CountingHashSet<'a>, HashSetDelta<'a>>>) : aset<'a> =
    ASet.custom (fun t -> f.Invoke(t).Invoke)

Simpler UpdateTo?

It feels like clist.UpdateTo could also have a simpler overload so for primitive values we can just call

            amodel.SKPoints.UpdateTo(model.SKPoints)

Separately, I wonder if ChangeableValue should have an UpdateTo for symmetry, perhaps where

            amodel.RefreshViewIsRefreshing.UpdateTo(model.RefreshViewIsRefreshing)

is equal to

            if model.RefreshViewIsRefreshing <> amodel.RefreshViewIsRefreshing.Value then 
                amodel.RefreshViewIsRefreshing.Value <- model.RefreshViewIsRefreshing

and with the more sophisticated overload available too

AList.init/AList.range and ranges of integers

From Discord:

Hey, a question - what's the best way to adaptively generate a range of integers, e.g. I have adaptive values min and max and I want an AList for min .. max?

Feels like we might be missing an AList.init : aval<int> -> (int -> 'T) -> alist<'T>? Possibly also an AList.range of some kind which might be more efficient?

ASet

migrate the aset functions from Aardvark.Base

Exception support in transaction?

Looking at this code it seems that transaction context might get corrupted in cases where the transaction body throws. I'm not too certain but it seems like the restoration statement should be placed in a finally clause.

What to do with HashSet and HashMap?

The system uses lots of datastructures like HashMap, HashSet, etc.

Since they need to be part of the public API we cannot make them internal.

My current approach is to leave HashMap, HashSet in the Incremental namespace and expose 'internal' things like CountingHashSet or History in the FSharp.Control.Traceable namespace.

That way users can explicity open and work with it, while still leaving the standard-API relatively clean.

Any suggestions?

History pruning

the current heuristic for pruning the History is suboptimal and the whole functionality is totally untested.

IndexList tests

add IndexList tests that would have revealed the bug i was hunting on the wrong end of my validation tests ๐Ÿ˜’

MapExt

should we port MapExt from Aardvark.Base or try to get its functions into FSharp.Core?

aval/aset/alist/amap non-generic

In our old implementation we had a IMod interface which provided GetValue : AdaptiveToken -> obj.
The reason for that mainly was to allow for heterogenous collections of changeable values in our rendering engine (uniform inputs for shaders, etc.)

I think that adding it would severely reduce the cleanness of the API but we need some way of capturing untyped avals for our rendering engine.

Basically we would need GADTs for representing these existential types but the .NET code simulating that is rather verbose:

type AValVisitor<'r> =
    abstract member Accept<'a> : aval<'a> -> 'r
type AVal =
    abstract member Visit : AValVisitor<'r> -> 'r

This would allow aval to get wrapped into one of these and we could 'extract' the generic Argument later on...

A different approach would be to explicitly 'upcast' to aval<obj> but since .NET doesn't support this we would need to create a new aval and use a mapping function, which really destroys the type-information and blows up the dependency graph.

Any better ideas for solving this problem in a cleaner way?

Independent WeakCallbacks

WeakCallbacks do not work as expected. All callbacks on an AdaptiveObject are hold by the MultiCallbackObject. Weak callbacks are not garbage collected as long as there is either any strong or weak callback alive. This can be demonstrated with the following test:

static void CreateGarbage(IAdaptiveValue<int> foo)
{
    foo.AddWeakMarkingCallback(() => Report.Line("WEAK OUT OF DATE"));
}

static void TestWeakCb()
{
    var myVal = AdaptiveValue.Init(5);
    var map = myVal.Map(x => x * 2);

    var foo = map.AddMarkingCallback(() => Report.Line("OUT OF DATE"));

    for (int i = 0; i < 100; i++)
        CreateGarbage(myMod);

    GC.Collect();
    GC.WaitForFullGCComplete();

    for (int i = 0; i < 1000; i++)
    {
        using (Adaptive.Transact)
        {
            myVal.Value = i;
        }
        Report.Line("GETVALUE: {0}", myVal.GetValue());

        Thread.Sleep(100);
    }
}

All the WeakCallbacks of CreateGarbage will be invoked together with the callback of the main test procedure.

missing ASet.groupBy

groupBy (f : 'a -> 'g) (set : aset<'a>) : amap<'g, HashSet<'a>>

At the moment I'm using a local utility function:

set |> ASet.map (fun v -> (f(v), v)) |> AMap.ofASet

Our old implementation had a GroupByReader. Would there be benefits implementing something similar? In any case adding grouping convenience utlities might be considered.

Fable compatibility

Known issues

  • WeakReference<'T> not available
  • HashSet<'T>/Dictionary<'T1, 'T2> not using hash-equality
  • typeof<'T> not working for generics (used by Cache and several other utilities)
  • OptimizedClosures.FSharpFunc<_,_,_>.Adapt(...) doesn't work
  • Monitor.Enter/Exit
  • Interlocked
  • Arguments cannot be passed byref
  • duplicate interface implementations (IComparable, IComparable<'T>)

MapExt tests

add some tests for neighbours, alter, choose2, mapMonotonic, chooseMonotonic, etc.

Fable type-tests

when overriding Equals the follwing happens:

override x.Equals o =
    match o with
        | :? HashSet<'T> as o -> ...
        | _ -> false

In fable this will cause a warning since it cannot test at runtime whether or not the generic 'T matches.
We currently avoid these warnings by simply unboxing the object, which may cause problems when using heterogenous HashSets/etc.

We could obviously just live with the warning but I tend to miss important things when fable just spams 50 warnings everytime it recompiles.

I'm creating this issue just so we don't forget...

Additional functions I find myself using

This issue is to catalog additional derived functions I find myself using. We can leave it open and other people can add them

List.existsA
List.forallA
AMap.find

Sample implementations, please improve:

module List =
   let existsA (f: 'T -> aval<bool>) xs =  AList.existsA f (AList.ofList xs)
   let forallA (f: 'T -> aval<bool>) xs =  AList.forallA f (AList.ofList xs)

module AMap =
    let find f x = AMap.tryFind f x |> AVal.map Option.get  // note should give a better error here

Missing operators

Keeping track of missing combinators here

Currently not implemented

  • AMap.ofASet
  • AList.try(At|Get)
  • AMap.tryFind
  • AMap.fold(Group|HalfGroup)
  • AList.ofAVal
  • ASet.sortWith
  • AList.toASet
  • ASet.ofAList
  • AList.sort
  • ASet.sort
  • ASet.mapToMap
  • AMap.keys
  • ASet.sortBy
  • AList.sortWith
  • AList.sortBy

Concurrent consistency

Problem

  • when evaluating an AdaptiveObject, it should see one consistent value for each input cell
  • consistency between multiple cells could then be achieved using tuples and maps on the tupled cell

Idea

  • every transaction colors objects it visits with some ref<Transaction> and (when finished) simply sets the ref to null in O(1)
  • every evaluation colors objects it evaluates with ref<EvaluationId> and once finished sets the ref to empty in O(1)
  • when visited by a new Transaction/Evaluation we prune all dead colors

Pro

  • that way we could identify objects where evaluation collides with a (not yet finished) Transaction and vice-versa.

Con

  • many leaking refs in objects (each object needs to hold many of those) and we can only clean them up when visiting the object again
  • what to do on collision? since the transaction may have marked a graph partially simply blocking it won't suffice. The only real alternative is to let the transaction continue and retry evaluation when it finished, potentially causing starvation on the evaluation-side.

Naming Convention

How should we name things consistently?

I personally like the lower-case names like (aset, amap, etc.) and would go for that one, but I'm open to other ideas.

type aref<'a> = //...
module ARef = // ...

The obvious choice would be AdaptiveRef, but the name is quite long and doesn't really roll off the tongue.

type AdaptiveRef<'a> = // ...
module AdaptiveRef = // ...

Maybe something with Incremental/Inc in it??

type IncRef<'a> = // ...
module IncRef = // ...

Any suggestions?

Reader disposal

any way to avoid disposable readers?
if not ensure that disposal gets rid of all unreferenced inputs.

FSharp.Core 4.7 support

I wanted to try the library and immediately found that the NuGet package has an constraint < 4.7.0 on FSharp.Core. I assumed that wasn't intentional, so I installed it by forcing the FSharp.Core version as == 4.7 on Paket, and the API seems to work fine. I guess the upper bound should be lifted?

Index

maybe migrate from Aardvark.Base or come up with a new one (real numbers like in aardvark.web)

Is IndexList.IndexOf complexity correct?

IndexList has IndexOf by Index operation:

/// Tries to find the position for the given Index or -1 if the Index does not exist. O(N)
member x.IndexOf(index : Index) =
    MapExt.tryIndexOf index content |> Option.defaultValue -1

Is its complexity really O(N)? I looked into the implementation and it seems to be a search in a binary tree, therefore having the O(log(N)) complexity, isn't it?

map with IDisposable -> mapUse

mapUse<'a, 'b when 'b :> IDisposable> (mapping : 'a -> 'b) (set : aset<'a>) : aset<'b>
mapUse<'a,'b when 'b : equality and 'b :> IDisposable> (mapping : 'a -> 'b) (list : alist<'a>)

Please consider restoring this functionality. Would this be also useful for AVal?

Derived AdaptiveObject in C#

At the moment it is not possible to implement a derived AdaptiveObject in C# since the inline type extension EvaluateAlways cannot be used from a C# environment.

We have discussed the possibility of adding a wrapper base class for C# that exposes this method. Please consider this extension.

PreCompiler

I (temporarily) put the PreCompiler code here since it's a lot easier to handle for the moment...
Nonetheless we should think about moving it to its own repo once it's done.

The current compilation-scheme looks like that:

Immutable Adaptive Remarks
ฦ’(HashSet<'T>) aset<ฦ’('T)> aset<aval<'T>> โ†’ aset<'T>
ฦ’(HashMap<'K, 'V>) amap<'K, ฦ’('V)> amap<'K, aval<'V>> โ†’ amap<'K, 'V>
ฦ’(IndexList<'T>) alist<ฦ’('T)> alist<aval<'T>> โ†’ alist<'T>
ฦ’(option<'T>) aval<option<ฦ’('T)>> options
ฦ’('T1 * 'T2 * ...) ฦ’('T1) * ฦ’('T2) * ... tuples
ฦ’({ a: 'T; ... }) { a: ฦ’('T); ... } records
ฦ’(|C of 'T1) aval<|AdaptiveC of ฦ’('T1)> union types.
Note that the aval<_> is needed
here whenever the union type has
more than one contstructor.
ฦ’('T) aval<'T> all other types

This basically reproduces the functionality of Aardvark.Compiler.DomainTypes which turned out to be quite useful in practice. Nonetheless It would be great to have some comments on that.
Any ideas, old problems that should be solved or other suggestions?
@ThomasOrtner @haraldsteinlechner @aszabo314 @dsyme

Oh and by the way: any naming suggestions? I think Adaptor would be nice, but it's not really self-explanatory...

AMap

migrate from Aardvark.Base

Consider hiding abstract members of AdaptiveMap and AdaptiveSet

Looking at these kind of type definitions:

[<Interface>]
type AdaptiveHashSet<'T> =
    /// is the set constant?
    abstract member IsConstant : bool

    /// the current content of the set as aval.
    abstract member Content : aval<HashSet<'T>>
    
    /// gets a new reader to the set.
    abstract member GetReader : unit -> IHashSetReader<'T>

I'm tempted to propose that we merge AdaptiveMap.fs and ChangeableMap.fs and hide the abstract members behind a signature. This would mean that no one can directly implement these interfaces - but it would also give us a lot of flexibility to change the implementations going forward - adding new members and so on. We can still provide a way to implement the interfaces by providing three function definitions.

Note that F# currently doesn't allow internal abstract members - you can only achieve this by hiding the whole type definition like this:

type AdaptiveHashMap<'Key,'Value> 

type amap<'Key,'Value> = AdaptiveHashMap<'Key,'Value>

AList.chooseAdaptive etc.

I found myself in need of an AList.chooseAdaptive, e,g,

val chooseAdaptive: ('T -> aval<'U option>) -> alist<'T> -> alist<'U>

I expect the same is true for existsAdaptive, mapAdaptive and so on. Some of these may already exist under different names.

Tests

missing tests for

  • amap
  • Index
  • IndexList
  • IndexListDelta

add `ASet.collect` with sequences

let collect (mapping : 'a -> #seq<'b>) (set : aset<'a>)

We might want to consider restoring this functionality.

At the moment this can be implemented quite easily manually and also always use the best ASet creator (ofSeq/ofArray/ofList). Would a special implementation allow any efficiency improvements or would it just be a convenience?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.