Смешное поведение Хаскелла: функция min на трех числах, включая отрицательную

Я играл с некоторыми функциями Haskell в GHCi.

Я получаю действительно забавное поведение, и мне интересно, почему это происходит.

Я понял, что функция min должна использоваться только с двумя значениями. Однако, когда я использую три значения, в моем случае

min 1 2 -5

Я получаю

-4

в качестве результата.

Почему это?

Ответ 1

Вы получаете этот результат, потому что это выражение:

min 1 2 -5

разбирает, как если бы это было в скобках, как это:

(min 1 2) -5

что так же, как это:

1 -5

что так же, как это:

1 - 5

что, конечно, -4.

В Haskell применение функций является наиболее жестко связанной операцией, но она не является жадной. На самом деле, даже такое, казалось бы, простое выражение, как min 1 2 фактически приводит к двум отдельным вызовам функций: функция min сначала вызывается с одним значением, 1; возвращаемое значение этой функции - новая анонимная функция, которая будет возвращать меньшее значение между 1 и единственным аргументом. Затем эта анонимная функция вызывается с аргументом 2 и, конечно, возвращает 1. Итак, более точная версия вашего кода с круглыми скобками выглядит так:

((min 1) 2) - 5

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

Таким образом, чтобы найти минимум три значения, вам нужно объединить два вызова в min (на самом деле четыре вызова в соответствии с приведенной выше логикой, но опять же, размахивая рукой):

min (min 1 2) (-5)

-5 вокруг -5 необходимы для того, чтобы - интерпретировалось как отрицание префикса вместо вычитания инфикса; без них у вас та же проблема, что и у исходного кода, только на этот раз вы попросите Haskell вычесть число из функции и получите ошибку типа.

В более общем смысле, вы можете позволить Haskell создать цепочку за вас, применив к списку складку, которая может содержать столько чисел, сколько вам нужно:

foldl1 min [1, 2, -5]

(Обратите внимание, что в синтаксисе литерального списка запятая и квадратная скобка отделяют -5, делая это явно не операцией вычитания, поэтому здесь вам не нужны скобки.)

Вызов foldl1 списка foldl1 означает: "возьмите первые два элемента списка и вызовите забаву для них. Затем возьмите результат этого вызова и следующий элемент списка и вызовите забаву для этих двух значений. Затем возьмите результат этого вызова и следующий элемент списка... "И так далее, пока не останется списка, после чего значение последнего вызова fun возвращается исходному вызывающему.

Для конкретного случая min, для вас уже определена сложенная версия: minimum. Таким образом, вы могли бы также написать выше, таким образом:

minimum [1, 2, -5]

Это ведет себя так же, как мое решение foldl1; в частности, оба выдадут ошибку, если получит пустой список, а если получит одноэлементный список, они вернут этот элемент без изменений, даже не вызывая min.

Спасибо JohnL за напоминание о существовании minimum.

Ответ 2

Когда вы вводите min 1 2 -5, Haskell не группирует его как min 1 2 (-5), как вы, кажется, думаете. Вместо этого он интерпретирует это как (min 1 2) - 5, т.е. Делает вычитание, а не отрицание. Минимум 1 и 2, очевидно, 1, и вычитая 5 из того, что будет (совершенно правильно), даст вам -4.

Как правило, в Haskell вы должны окружать отрицательные числа круглыми скобками, чтобы такие вещи не случались неожиданно.

Ответ 3

Ничего не добавить к предыдущим ответам. Но вы, вероятно, ищете эту функцию.

import Data.List
minimum [1, 2, -4]