Какая разница между "equal (=)" и "same (==)" в ocaml?

В OCaml мы имеем два вида equity comparisons:

x = y и x == y,

Итак, какая именно разница между ними?

Является ли это x = y в ocaml точно так же, как x.equals(y) в Java?

и x == y точно так же, как x == y (comparing the address) в Java?

Ответы

Ответ 1

Я точно не знаю, как x.equals(y) работает в Java. Если это "глубокое" сравнение, то аналогия довольно близка. Одна вещь, о которой нужно помнить, заключается в том, что физическое равенство является скользкой концепцией в OCaml (и функциональных языках вообще). Компилятор и среда выполнения собираются перемещать значения вокруг и могут объединять и разворачивать чистые (не изменяемые) значения по желанию. Поэтому вы должны использовать ==, если вы действительно знаете, что делаете. На каком-то уровне это требует знакомства с реализацией (чего следует избегать, если это необходимо).

Конкретные гарантии, которые OCaml делает для ==, слабы. Сопротивляемые значения сравниваются как физически равные в том виде, в котором вы ожидаете (т.е. Если мутация одного из двух будет фактически мутировать и другое). Но для неизменяемых значений единственной гарантией является то, что значения, сравнивающие физически равные (==), также будут сравниваться как равные (=). Обратите внимание, что обратное неверно, поскольку sepp2k указывает на плавающие значения.

По сути, то, что спецификация языка говорит вам о неизменяемых значениях, заключается в том, что вы можете использовать == как быструю проверку, чтобы решить, равны ли два не изменяемых значения (=). Если они сравнивают физически равные, они равны по величине. Если они не сравниваются физически одинаково, вы не знаете, насколько они равны по размеру. Вы все равно должны использовать =, чтобы решить.

Ответ 2

Изменить: этот ответ подробно описывает внутреннюю работу OCaml на основе модуля Obj. Это знание не предназначено для использования без дополнительной помощи (позвольте мне еще раз подчеркнуть этот очень важный момент: не используйте его для своей программы, но только если вы хотите поэкспериментировать с OCaml во время выполнения). Эта информация также доступна, хотя, возможно, в более понятной форме в книге O'Reilly на OCaml, доступной в Интернете (довольно хорошая книга, хотя бит).

Оператор = проверяет структурное равенство, тогда как == проверяет только физическое равенство.

Проверка равенства основана на том, как распределяются значения и хранятся в памяти. Среднее время выполнения в OCaml может примерно соответствовать двум различным категориям: в коробке или без коробки. Первое означает, что значение доступно в памяти через косвенное направление, а позднее означает, что значение напрямую доступно.

Так как int (int31 на 32-битных системах или int63 на 64-битных системах) являются незанятыми значениями, оба оператора ведут себя одинаково с ними. Несколько других типов или значений, реализация которых на самом деле int, также будут видеть, что оба оператора ведут себя одинаково с ними, например unit (), пустой список [], константы в алгебраических типах данных и полиморфных вариантах и ​​т.д.

Как только вы начнете играть с более сложными значениями, связанными с структурами, такими как списки, массивы, кортежи, записи (эквивалент C-структуры), возникает разница между этими двумя операторами: значения внутри структур будут помещены в бокс, если они не могут быть представлены во время выполнения как родной ints (1). Эта необходимость возникает из-за того, как среда выполнения должна обрабатывать значения и эффективно управлять памятью. Структурированные значения выделяются при построении из других значений, которые могут быть самими структурированными значениями, и в этом случае используются ссылки (поскольку они помещены в коробку).

Из-за распределений очень маловероятно, что два значения, созданные в разных точках программы, могут быть физически равными, хотя они будут структурно равными. Каждое из полей или внутренних элементов внутри значений может быть идентичным, даже до физической идентичности, но если эти два значения будут построены динамически, тогда они будут использовать разные пространства в памяти и, следовательно, будут физически разными, но структурно равными.

Время выполнения пытается избежать ненужных распределений: например, если у вас есть функция, возвращающая всегда одно и то же значение (другими словами, если функция является постоянной), либо простой, либо структурированной, эта функция всегда будет возвращать одно и то же физическое (т.е. одни и те же данные в памяти), так что при тестировании для физического равенства результат двух вызовов этой функции будет успешным.

Один из способов наблюдения, когда физический оператор действительно вернет true, - это использовать функцию Obj.is_block в представлении времени выполнения (то есть результат Obj.repr на нем). Эта функция просто указывает, помещено ли его представление времени выполнения.

Более надуманным способом является использование следующей функции:

let phy x : int = Obj.magic (Obj.repr x);;

Эта функция вернет значение int, которое является фактическим значением указателя на значение, привязанное к x в памяти, если это значение вставляется в квадрат. Если вы попробуете его в литерале int, вы получите то же самое значение! Это потому, что int unboxed (т.е. Значение хранится непосредственно в памяти, а не через ссылку).

Теперь, когда мы знаем, что значения в штучной упаковке являются фактически "ссылочными" значениями, мы можем сделать вывод, что эти значения могут быть изменены, хотя язык говорит, что они неизменяемы.

рассмотрим, например, ссылочный тип:

# type 'a ref = {mutable contents : 'a };;

Мы могли бы определить неизменяемый ref:

# type 'a imm = {i : 'a };;
type 'a imm = {i : 'a; }

И затем используйте функцию Obj.magic, чтобы принудить один тип к другому, потому что структурно эти типы будут сведены к одному представлению времени исполнения.

Например:

# let x = { i = 1 };;
- : val x : int imm = { i = 1 }
# let y : int ref = Obj.magic x;;
- : val y : int ref = { contents = 1 }
# y := 2;;
- : unit = ()
# x
- : int imm = { i = 2 }

Есть несколько исключений из этого:

  • если значения являются объектами, тогда даже кажущиеся структурно идентичными значения возвращают false при структурном сравнении

    # let o1 = object end;;
    val o1 : < > = <obj>
    # let o2 = object end;;
    val o2 : < > = <obj>
    # o1 = o2;;
    - :  bool = false
    # o1 = o1;;
    - :  bool = true
    

    здесь мы видим, что = возвращается к физической эквивалентности.

  • Если значения являются функциями, вы не можете сравнивать их структурно, но физическое сравнение работает по назначению.

  • ленивые значения могут быть или не быть структурно сопоставимыми, в зависимости от того, были ли они принудительными или нет (соответственно).

    # let l1 = lazy (40 + 2);;
    val l1 : lazy_t = <lazy>
    # let l2 = lazy (40 + 2);;
    val l2 : lazy_t = <lazy>
    # l1 = l2;;
    Exception: Invalid_argument "equal: functional value".
    # Lazy.force l1;;
    - :  int = 42
    # Lazy.force l2;;
    - :  int = 42
    # l1 = l2;;
    - :  bool = true 
    
  • или значения записи также сопоставимы, если они не содержат никакого функционального значения.

В общем, я полагаю, что можно с уверенностью сказать, что значения, связанные с функциями, или могут удерживать функции внутри, не сопоставимы с =, но их можно сравнить с ==.

Вы должны явно быть очень осторожным со всем этим: полагаться на детали реализации среды выполнения некорректно (Примечание: я в шутку использовал слово зло в своей первоначальной версии этого ответа, но изменил его опасаясь, что это будет восприниматься слишком серьезно). Как вы точно указали в комментариях, поведение реализации javascript отличается для поплавков (структурно эквивалентно в javascript, но не в эталонной реализации, а что касается java-одного?).

<ч/" > (1) Если я правильно помню, поплавки также распаковываются при хранении в массивах, чтобы избежать двойной косвенности, но после этого они становятся в коробке, поэтому вы не должны видеть разницу в поведении со значениями в коробке.

Ответ 3

Является ли x = y в ocaml так же, как x.equals(y) в Java?

и x == y как x == y (сравнение адреса) в Java?

Да, это так. За исключением того, что в OCaml вы можете использовать = для каждого типа значения, тогда как в Java вы не можете использовать equals для примитивных типов. Другое отличие состоит в том, что числа с плавающей запятой в OCaml являются ссылочными типами, поэтому вы не должны сравнивать их с помощью == (не так, как правило, хорошая идея сравнивать числа с плавающей запятой непосредственно для равенства в любом случае).

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