Skip to content

Equality

GraphOptions.Equality sets the comparison for every signal and memo in the graph.

  • JsIdentityPolicy, the default, follows JavaScript ===.
  • StructuralPolicy uses EqualityComparer<'T>.Default.
How the policies compare values on .NET
Value kindJsIdentityPolicy (default)StructuralPolicy
Primitives, strings, structs, struct tuplesBy valueBy value
Records, unions, Some x, other reference typesBy reference: an equal copy propagatesBy value: an equal copy is cut off
None over NoneCut off (None is null)Cut off
nan over nanPropagates (IEEE: nan equals nothing)Cut off
0.0 over -0.0Cut offCut off
Compare the policies on .NET

This example counts the effect runs caused by each write, excluding the initial run.

type Point = { X: int; Y: int }

let countWakes (options: GraphOptions) (first: 'T) (second: 'T) =
    use graph = new Graph (options)
    use _ = graph.Activate ()
    let source = createSignal first
    let mutable runs = 0
    createEffect (fun () -> source.Value |> ignore; runs <- runs + 1)
    source.Value <- second
    runs - 1

let structural = { GraphOptions.Default with Equality = StructuralPolicy () }

let equalityRows =
    [ "equal record", countWakes GraphOptions.Default { X = 1; Y = 2 } { X = 1; Y = 2 },
                      countWakes structural { X = 1; Y = 2 } { X = 1; Y = 2 }
      "Some 1 over Some 1", countWakes GraphOptions.Default (Some 1) (Some 1),
                            countWakes structural (Some 1) (Some 1)
      "None over None", countWakes GraphOptions.Default (None: int option) None,
                        countWakes structural (None: int option) None
      "nan over nan", countWakes GraphOptions.Default nan nan, countWakes structural nan nan
      "0.0 over -0.0", countWakes GraphOptions.Default 0.0 -0.0, countWakes structural 0.0 -0.0 ]

for name, js, st in equalityRows do
    printfn "%-20s default wakes: %d, structural wakes: %d" name js st
equal record         default wakes: 1, structural wakes: 0
Some 1 over Some 1   default wakes: 1, structural wakes: 0
None over None       default wakes: 0, structural wakes: 0
nan over nan         default wakes: 1, structural wakes: 0
0.0 over -0.0        default wakes: 0, structural wakes: 0
Differences between .NET and Fable

The table below shows whether writing an equal but separately built value is cut off. Some value types, including DateTime and decimal, compile to objects under Fable, so the default comparison can differ between targets.

Writing the same object instance back is cut off on both targets.

TypeDefault on .NETDefault under FableStructuralPolicy, both targets
int, float, stringCut offCut offCut off
nanPropagatesPropagatesCut off on .NET, propagates under Fable
DateTime, DateTimeOffset, decimalCut offPropagatesCut off
Struct records, struct tuplesCut offPropagatesCut off
Some 1PropagatesCut offCut off
NoneCut offCut offCut off
Records, tuples, lists, Some of a recordPropagatesPropagatesCut off
Class without custom equalityPropagatesPropagatesPropagates
Define a custom equality policy

Pass an IEqualityPolicy as GraphOptions.Equality. Its Comparer<'T> supplies the comparer for each value type and is called once when a node is created. Individual nodes cannot take their own comparers.

For example, this graph treats strings that differ only in case as equal:

type CaseInsensitivePolicy() =
    interface IEqualityPolicy with
        member _.Comparer<'T>() =
            { new System.Collections.Generic.IEqualityComparer<'T> with
                member _.Equals(a, b) =
                    System.String.Equals (string (box a), string (box b), System.StringComparison.OrdinalIgnoreCase)
                member _.GetHashCode a = (string (box a)).ToLowerInvariant().GetHashCode () }

let caseInsensitiveWakes =
    countWakes { GraphOptions.Default with Equality = CaseInsensitivePolicy () } "abc" "ABC"

printfn "case-insensitive wakes: %d" caseInsensitiveWakes
case-insensitive wakes: 0
Edit this page