title: Generic Equals implementation
body: <p>I have several generic equality functions, which are used when overriding <code>Object.Equals</code>:</p>
  
  <pre><code>type IEqualityComparer&lt;'T&gt; = System.Collections.Generic.IEqualityComparer&lt;'T&gt;
  
  let equalIf f (x:'T) (y:obj) =
    if obj.ReferenceEquals(x, y) then true
    else
      match box x, y with
      | null, _ | _, null -&gt; false
      | _, (:? 'T as y) -&gt; f x y
      | _ -&gt; false
  
  let equalByWithComparer (comparer:IEqualityComparer&lt;_&gt;) f (x:'T) (y:obj) = 
    (x, y) ||&gt; equalIf (fun x y -&gt; comparer.Equals(f x, f y))
  </code></pre>
  
  <p>Typical usage would be:</p>
  
  <pre><code>type A(name) =
    member __.Name = name
    override this.Equals(that) = 
      (this, that) ||&gt; equalByWithComparer StringComparer.InvariantCultureIgnoreCase (fun a -&gt; a.Name)
  
  type B(parent:A, name) =
    member __.Parent = parent
    member __.Name = name
    override this.Equals(that) = (this, that) ||&gt; equalIf (fun x y -&gt;
      x.Parent.Equals(y.Parent) &amp;&amp; StringComparer.InvariantCultureIgnoreCase.Equals(x.Name, y.Name))
  </code></pre>
  
  <p>I'm mostly happy with this. It reduces <a href="http://en.wikipedia.org/wiki/Boilerplate_code" rel="nofollow">boilerplate<sup>[wikipedia]</sup></a>. But I'm annoyed having to use <code>equalBy</code> instead of the more concise <code>equalByWithComparer</code> in type <code>B</code> (since its equality depends on its parent's). </p>
  
  <p>It feels like it should be possible to write a function that accepts a reference to the parent (or 0..N projections), which are checked for equality using <code>Equals</code>, along with a property to be checked and its accompanying comparer, but I've yet been unable imagine its implementation. Perhaps all this is overdone (not sure). How might such a function be implemented?</p>
  
  <h3>EDIT</h3>
  
  <p>Based on Brian's answer, I came up with this, which seems to work okay.</p>
  
  <pre><code>let equalByProjection proj (comparer:IEqualityComparer&lt;_&gt;) f (x:'T) (y:obj) = 
    (x, y) ||&gt; equalIf (fun x y -&gt; 
      Seq.zip (proj x) (proj y)
      |&gt; Seq.forall obj.Equals &amp;&amp; comparer.Equals(f x, f y))
  
  type B(parent:A, otherType, name) =
    member __.Parent = parent
    member __.OtherType = otherType //Equals is overridden
    member __.Name = name
    override this.Equals(that) = 
      (this, that) ||&gt; equalByProjection
        (fun x -&gt; [box x.Parent; box x.OtherType])
        StringComparer.InvariantCultureIgnoreCase (fun b -&gt; b.Name)
  </code></pre>