Почему OCaml ( ) не полиморфен?

#polymorphism #ocaml #addition

#полиморфизм #ocaml #добавление

Вопрос:

Я новичок в OCaml. Мне нравится скорость OCaml, но я не до конца понимаю его дизайн. Например, я хотел бы, чтобы оператор был полиморфным для поддержки целых чисел, чисел с плавающей точкой и так далее.

Зачем нам это нужно . ?

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

1. В названии вашего вопроса утверждается, что речь идет о синтаксисе, и тогда единственный пример ошибки, который вы приводите, касается системы типов. Взгляните на вопросы StackOverflow по поводу «[C] странного поведения». Многие из них вызваны системой типов C. float f = 3 / 7; устанавливает f в ноль. sizeof(int) - 5 не -1 . Ну, OCaml не использует эту проклятую систему. Вопрос должен быть в том, почему так много языков все еще используют его, когда это озадачивает так много людей?

2. Это отличный вопрос, и я очень разочарован тем, что он закрыт. По этому поводу можно сделать много объективных заявлений. Разделение и . делает вывод типа более простым и предсказуемым. Альтернативы менее предсказуемы (значения по умолчанию) или потенциально намного менее эффективны (отправка). Тогда возникает вопрос о том, будет ли это злоупотреблением перегрузкой, учитывая, что функции имеют разные характеристики (например, ассоциативность) или даже совершенно разные цели (деление против евклидова коэффициента).

Ответ №1:

Я бы хотел, чтобы оператор ‘ ‘ был полиморфным для поддержки целых чисел, чисел с плавающей запятой и так далее. Зачем нам ‘ .’?

Отличный вопрос. Здесь задействовано много тонких компромиссов.

Преимущества отсутствия перегрузки операторов (как в OCaml) заключаются в:

  • Вывод типа проще и предсказуем.
  • Код более композиционный: перемещение кода из одного места в другое не может повлиять на его значение.
  • Предсказуемая производительность: вы всегда точно знаете, какая функция вызывается.

Недостатками являются:

  • Количество различных операторов быстро выходит из-под контроля: для int , . для float , / для рациональных чисел произвольной точности, | для векторов, || для матриц и комплексных чисел, низкоразмерных векторов и матриц, однородных координат и т.д.

Некоторые альтернативы:

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

1. Классы типов не обязательно должны разрешаться во время выполнения. Существуют также прагмы, гарантирующие разрешение во время компиляции. Методы класса типа могут нести значение, поэтому код не должен изменять значение в другом контексте. Вывод типа также предсказуем и всеобъемлющ в их присутствии. Проблема, кстати, не только в большом количестве операторов, но и в сложности написания универсального кода. Функция, использующая полиморфные операторы, будет более полезной и более повторно используемой, чем функция, жестко закодированная для определенного типа.

2. @Peaker «Классы типов не обязательно разрешать во время выполнения». Как вы разрешаете все классы типов во время компиляции, когда код может быть загружен динамически?

3. @Peaker «Функция, использующая полиморфные операторы, будет более полезной и более многократно используемой, чем функция, жестко закодированная для определенного типа». Арифметика является наиболее распространенным источником перегруженных операторов. Целые числа и числа с плавающей точкой являются наиболее распространенными числовыми типами. Численные методы с целыми числами и числами с плавающей запятой не имеют почти ничего общего, поскольку семантика перегруженных операторов отличается между этими числовыми типами, в первую очередь из-за округления в арифметике с плавающей запятой. Итак, классы типов позволяют вам учитывать общность, но там мало общности, которую нужно учитывать.

4. Вы можете использовать СПЕЦИАЛИЗИРОВАННЫЕ программы и давать конкретные типы. Вы можете оставить это для среды выполнения, и если нет способа узнать типы до времени выполнения, это будет оставлено для среды выполнения. Это особенность, а не ошибка.

5. Я не уверен, что арифметика является наиболее распространенным источником перегруженных операторов. Также (>>=), (>>), (<>) и многие другие неарифметические перегрузки. Я согласен, что целые числа и числа с плавающей точкой не настолько похожи, и действительно, они используют разные классы типов. Но Int, {Int, Word}{8,16,32,64}, Integer — все очень похожие типы. Float и Double имеют много общего.

Ответ №2:

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

Помимо . версий числовых операторов, я не думаю, что синтаксис OCaml особенно странный. Он во многом соответствует предыдущим языкам ML с соответствующими и разумными расширениями синтаксиса для его дополнительных функций. Если поначалу это кажется вам странным, это, вероятно, просто указывает на то, что до сих пор вы программировали только на языках с тесно связанным синтаксисом. По мере изучения новых языков вы увидите, что существует множество различных способов создания языкового синтаксиса с различными преимуществами и недостатками, но многое из этого — просто произвольные соглашения, которые кто-то выбрал.

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

1. » Versus . thing устраняет множество мелких ошибок». Если бы это было правдой, код F # страдал бы от этих тонких ошибок, но это не так. На самом деле ошибки почти полностью вызваны неявными приведениями (например, 2.3 / 0 или 1/12 * 123.456), а не перегрузкой. F # не выполняет неявных приведений, т. е. является int -> int -> int или, float -> float -> float но не int -> float -> float . Так что это не является веским мотивом для выбора OCaml.

2. «Помимо . версии числовых операторов». И не забывайте, что . версии предназначены специально для float числового типа. OCaml также предоставляет / возможность добавления произвольной точности, и мне приходилось писать свою собственную | версию для векторов и || версию для матриц, что очень быстро становится смешным. И OCaml даже не предлагает большинство числовых типов, таких как 32-разрядные числа с плавающей запятой…

3. Этот аргумент выглядит разумным только до тех пор, пока вы не начнете учитывать все другие компромиссы, которые F # пришлось сделать, чтобы поддержать эту перегрузку.

4. Вы правы, что это действительно неявное приведение, которое вызывает большинство незначительных ошибок, и что вы можете выполнять полиморфные операторы без неявного приведения, которые не вызывают такого беспокойства. Но как только вы разрешаете полиморфные операторы, в общем, тогда люди, определяющие свои собственные, могут создавать операции int -> int -> int, float -> float -> float и int -> float -> float. Единственный способ предотвратить это — разрешить только полиморфные операторы для стандартных библиотек или какой-либо другой такой специальной оболочки. Имеет смысл иметь единые правила.

5. Некоторое другое обсуждение причин. Модульные импликации — это предложение добавить перегрузку в OCaml. Классы модульных типов — это (всего лишь) теория потенциального добавления классов типов в ML-подобный язык.

Ответ №3:

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

Некоторые языки семейства ML имеют чрезвычайно ограниченные формы специального полиморфизма в числовых операторах. Например, ( ) в стандартном ML имеет тип по умолчанию (int, int) -> int , но имеет тип (float, float) -> float , если его аргумент или возвращаемый тип известен как float . Эти операторы являются особыми в SML и не могли быть определены, если они еще не были встроены. Также невозможно присвоить этому свойству другие значения. val add = ( ) будет иметь тип (int, int) -> int . Особенность здесь ограничивается синтаксисом языка, в системе типов нет поддержки специального полиморфизма.

В OCaml есть несколько операторов со специальной семантикой (но числовые операторы не входят в их число), например, || и amp;amp; имеют короткое замыкание (но становятся длинным замыканием, если вы присваиваете им промежуточное значение)

 let long_circuit_or = (||);;
let print_true x = print_string x; true;;
(* just prints "4" *)
print_true "4" || print_true "5";;
(* prints "45" *)
long_circuit_or (print_true "4") (print_true "5");;