Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A Changeable Value that can be depend on other adaptive values #114

Open
patrickallensimpson opened this issue Aug 8, 2023 · 0 comments

Comments

@patrickallensimpson
Copy link

patrickallensimpson commented Aug 8, 2023

My Team has been using the Fun.Blazor library to do a Blazor UI. In the process we've come across two scenarios in which we'd like a ChangeableValue to depend on another IAdaptiveValue.

  • In the first scenario, We have an incoming ChangeableValue that we want to delay propagating the Mark and Values to until the child has been Committed. We've been calling it DependantChangeableValue<'T> with an implementation like this:
type DependantChangeableValue<'T>(antecedent: cval<'T>) =
    inherit AVal.AbstractVal<'T>()

    // can we avoid double caching (here and in AbstractVal)
    let mutable cache: ValueOption<'T> = ValueNone
        
    override x.Compute(token: AdaptiveToken) =
        match cache with
        | ValueSome v -> v
        | _ ->
            antecedent.GetValue token

    member x.SetValue (v:'T) =
        transact(fun _ ->
            cache <- ValueSome v
            x.MarkOutdated())

    member x.UpdateAntecedent() =
        transact(fun _ ->
            match cache with
            | ValueSome v -> 
                antecedent.Value <- v
            | _ -> ())

    member x.WithSetter() =
        x |> AVal.map(fun v -> v,x.SetValue)
  • In the other case we have a UI component that returns a ChangeableValue which represents the result of the components behavior. It's a Changable value because we want other components or systems to be able to change the UI component by changeing this output (think a selected list where the selected item is 'T option and you can clear the selection by setting to None). But I'd also like to have an input IAdaptiveValue and a setter function able to be optionally passed in that would be able to participate in the Changeable value Mark and SetValue. In this way I might be able to route the SetValue through the returned changeable to the setter function.

In both cases we can definitely implement those capabilities with the current Library but they can be used interchangeably with cval. And it turns out that having the ability to represent changeabilty as type would be sort of nice.

So my question I think it might be useful to to have either an IChangeableValue that derives from IAdaptiveValue and provides the hooks so that code that sees cval can work with any particular implementation or unseal ChangeableValue to allow extension for the same reason. I think the IChangeableValue might be the better route but I wanted some feedback about the suggestion.

Another type we also implemented for representing Async computations that might need to be refreshed after you use them (ex. Database queries with the same arguments):

    type AsyncStatus<'T> =
    | Init
    | Ready of data:'T
    | Loading of msg:string*elapsed:System.TimeSpan
    | Error of msg:string*when':System.DateTimeOffset
    [<StructuredFormatDisplay("{AsString}")>]
    type AsyncChangeableValue<'T>(async':'T Async,?loadingMessage:string,?requireRefresh:bool) as this =
        inherit AdaptiveObject()

        // the initial value is AsyncStatus<'T>.Init
        // thier is also a quiet cache used to attempt refreshing without triggering a change notification until finished

        member x.Value with get                
        member x.GetValue (token: AdaptiveToken) : AsyncStatus<'T>
        /// the async version allows the user to await until the operation is completed to continue with another operation.
        /// if a RefreshQuiet was in progress then it is turned into a normal Refresh operation
        /// if a RefreshAsync in already in flight then this returns an Async<unit> that can be waiting on until the original operation
        /// complete
        /// This cause a Refresh to immediately start and the returned Async<unit> is only used to synchronize with the results. Think Async.StartAsChild(...)
        member x.RefreshAsync() : Async<unit>
        /// this version triggers a refresh and then immediately returns (fire and forget), internally it fires a RefreshAsync and then ignore the resulting Async<unit>
        member x.Refresh() : unit
        /// will cause a refresh of the async computation but will NOT change the state of the cached data
        /// unless it successfully completes. updateOnlyWhenChanged will execute an expensive comparison on
        /// the incoming value to only mark this aval as changed if it is not equal to the current value
        /// ignoreErrors will NOT cause a Change in value if an Error occurs, instead the current value is retained
        /// if a RefreshAsync was in progress then this returns an Async<unit> that can be waited on until the original operation is complete
        /// if a RefreshQuietAsync in already in flight then this returns an Async<unit> that can be waiting on until the original operation
        /// completes
        /// This cause a Refresh to immediately start and the returned Async<unit> is only used to synchronize with the results. Think Async.StartAsChild(...)
        member x.RefreshQuietAsync([<Optional;DefaultParameterValue(false)>]ignoreErrors,[<Optional;DefaultParameterValue(false)>]updateOnlyWhenChanged) : Async<unit>
        /// triggers a RefreshQuietAsync operation and then immediately returns, ignoring the Async<unit> result of RefreshQuiet.
        member x.RefreshQuiet([<Optional;DefaultParameterValue(false)>]ignoreErrors) = x.RefreshQuietAsync(ignoreErrors) |> Async.Start
        member result.MapWithDefault (mapping:'T -> 'U,?defaultValue:'U) : aval<'U>
        member result.IsErrored : aval<bool>
        member result.IsLoading : aval<bool>
        member result.IsReady : aval<bool>
        member result.ReadyValue : aval<'T option>
        member result.LoadingMessage : aval<(string * System.TimeSpan) option>
        member result.ErrorMessage : aval<(string * System.DateTimeOffset) option>


        interface IAdaptiveValue with
            ...

        interface IAdaptiveValue<DataStatus<'T>> with
            ...
        
        member private x.AsString = sprintf "acval(%A)" x.Value
        override x.ToString() = System.String.Format("acval({0})", x.Value)

    and acval<'T> = AsyncChangeableValue<'T>

Thanks in advance for any comments and suggestions.

-Patrick

I just saw this discussion which did discuss something like the AsyncChangeableValue type above: #101 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant