Перегрузка оператора равенства F # при различаемом объединении с кортежем дает неожиданный результат

#.net #f# #functional-programming #operator-overloading #equality

#.net #f# #функциональное программирование #оператор-перегрузка #равенство

Вопрос:

Хорошо, итак, чтобы сделать сложный заголовок более понятным: у меня есть объединение с одним регистром, которое является общим кортежем. Тип также перегружает оператор равенства с намерением сделать что-то вроде Edge (1, 2) эквивалента Edge (2, 1) .

 type Edge<'a> = Edge of 'a * 'a
    with
        static member (=) (e1: Edge<_>, e2: Edge<_>) =
            match e1, e2 with
            | Edge(a,b), Edge(c,d) ->
                (a = c amp;amp; b = d) || (a = d amp;amp; b = c)
  

Однако, когда я создаю два значения типа Edge, которые должны быть эквивалентными, и сравниваю их, он возвращает false .

 > let e1 = Edge (1,2);;

val e1 : Edge<int> = Edge (1,2)

> let e2 = Edge (2,1);;

val e2 : Edge<int> = Edge (2,1)

> e1 = e2;;

val it : bool = false
  

Я в недоумении относительно того, что здесь на самом деле происходит. Есть ли что-то особенное в операторе равенства (=), что делает его более сложным для переопределения, чем другие операторы?

Комментарии:

1. Стоит отметить, что это абсолютно ужасная GetHashCode реализация, поскольку в любой момент a = b она вернется 0 .

2. @ildjarn прошло довольно много времени, и я вернулся к этому вопросу, но вы правы в этом замечании. Я удалил этот фрагмент моего примера кода, чтобы никто не копировал и не вставлял его вслепую.

Ответ №1:

Итак, в сообщении об ошибке была подсказка (которую вы не опубликовали)

предупреждение FS0086: имя ‘(=)’ не должно использоваться в качестве имени члена. Чтобы определить семантику равенства для типа, переопределите элемент ‘Object.Equals’. Если вы определяете статический элемент для использования на других языках командной строки, вместо этого используйте имя ‘op_Equality’.

На самом деле вы можете проверить, добавив printf вызовы в свой исходный код, и вы обнаружите, что он на самом деле никогда не вызывается.

Итак, тогда вы пытаетесь это:

 type Edge<'a> = Edge of 'a * 'a
     with
         override x.Equals (y: obj ) =
             match  y with
             | :? Edge<'a> as e ->
                 match x,e with
                 |Edge(a,b),Edge(c,d)->(a = c amp;amp; b = d) || (a = d amp;amp; b = c)
             | _ -> false
  

что также приводит к сбою. Затем сообщения об ошибках компилятора приведут вас к еще нескольким сообщениям об ошибках, пока вы не дойдете до

 [<CustomEquality;NoComparison>]
type Edge<'a when 'a:equality> = Edge of 'a * 'a
    with
        override x.Equals (y: obj ) =
            match  y with
            | :? Edge<'a> as e ->
                match x,e with
                |Edge(a,b),Edge(c,d)->(a = c amp;amp; b = d) || (a = d amp;amp; b = c)
            | _ -> false
  

который работает просто отлично. Функция equals немного сложнее, чем многие другие операторы, поэтому для ее переопределения требуется немного больше усилий.

Комментарии:

1. Спасибо, Джон. Впоследствии я пошел по этому кроличьему следу после публикации вопроса и почти добрался туда, но ваш ответ привел меня к рабочему выводу!