Является ли оператор && строгим в Haskell?

Например, у меня есть операция fnB :: a -> Bool, которая не имеет смысла, пока fnA :: Bool не вернет False. В C я могу объединить эти две операции в один if блок:

if( fnA && fnB(a) ){ doSomething; }

а C гарантирует, что fnB не будет выполняться до тех пор, пока fnA не вернет false.

Но Haskell ленив, и, как правило, нет гарантии, какая операция выполнится первой, пока мы не используем seq, $! или что-то еще, чтобы сделать наш код строгим. В общем, это то, что нам нужно для счастья. Но, используя оператор &&, я ожидаю, что fnB не будет оцениваться, пока fnA не вернет свой результат. Предоставляет ли Haskell такую ​​гарантию с &&? И будет ли Haskell оценивать fnB, даже если fnA возвращает False?


person Dmitry Bespalov    schedule 30.09.2011    source источник


Ответы (4)


Функция (&&) является строгой по второму аргументу, только если ее первый аргумент равен True. Он всегда строг в своем первом аргументе. Вот эта строгость/лень и гарантирует порядок оценивания.

Так что он ведет себя точно так же, как C. Разница в том, что в Haskell (&&) — это обычная функция. В C это было бы невозможно.

Но Haskell ленив, и, как правило, нет гарантии, какая операция выполнится первой, пока мы не используем seq, $! или что-то еще, чтобы сделать наш код строгим.

Это неправильно. Истина глубже.

Ускоренный курс строгости:

Мы знаем, что (&&) является строгим в своем первом параметре, потому что:

⊥ && x = ⊥

Здесь ⊥ — это что-то вроде undefined или бесконечного цикла (⊥ произносится как «нижний»). Мы также знаем, что (False &&) не является строгим во втором аргументе:

False && ⊥ = False

Он не может оценить свой второй аргумент, потому что его второй аргумент равен ⊥, который не может быть оценен. Однако функция (True &&) является строгой по второму аргументу, потому что:

True && ⊥ = ⊥

Итак, мы говорим, что (&&) всегда является строгим по первому аргументу и строгим по второму аргументу только тогда, когда первый аргумент равен True.

Порядок оценки:

Для (&&) свойств строгости достаточно, чтобы гарантировать порядок выполнения. Это не всегда так. Например, (+) :: Int -> Int -> Int всегда является строгим в обоих аргументах, поэтому любой аргумент может быть оценен первым. Однако вы можете заметить разницу, только перехватывая исключения в монаде IO или используя функцию unsafe.

person Dietrich Epp    schedule 30.09.2011
comment
На самом деле, это никогда не бывает строгим во втором аргументе. Он возвращает это без оценки. - person Fred Foo; 30.09.2011
comment
@larsmans: На самом деле любая функция, которая возвращает значение без его оценки, по определению является строгой в этом аргументе. Например, функция id всегда строга в своем аргументе. - person Dietrich Epp; 30.09.2011
comment
Этот ответ, кажется, предполагает последовательную оценку. Реализация может законно оценивать оба аргумента параллельно, пока первый получает свою долю ресурсов, а исключения из второго обрабатываются должным образом. Это была бы удивительная реализация, но вполне законная. - person dfeuer; 01.09.2016
comment
@dfeuer: Хорошо, если мы собираемся быть педантичными в этом вопросе… Во-первых, вопрос конкретно касается порядка оценки, поэтому мы должны поговорить об этом, чтобы ответить на вопрос. Во-вторых, если мы говорим о порядке вычисления по отношению к чистой семантике Haskell, мы можем сказать, что, поскольку вычисление ⊥ невозможно, False && ⊥ не должен оценивать ⊥, поэтому он должен сначала оценить False, иначе он не знал бы, что это не предполагается. оценить ⊥. Таким образом, невозможно оценить False && ⊥ параллельно. Однако вы можете написать параллельную программу с общей памятью, которая даст тот же результат. - person Dietrich Epp; 01.09.2016
comment
@FredFoo - технически Haskell не позволяет «возвращать значение без его оценки»; && tail вызывает свой второй аргумент, когда первый равен True. - person Jonathan Cast; 16.02.2018

Как отмечали другие, естественно, (&&) является строгим в одном из своих аргументов. По стандартному определению он строг в своем первом аргументе. Вы можете использовать flip, чтобы перевернуть семантику.

В качестве дополнительного примечания: обратите внимание, что аргументы (&&) не могут иметь побочных эффектов, поэтому есть только две причины, по которым вам нужно заботиться о том, является ли x && y строгим в y:

  • Производительность: если y вычисление занимает много времени.
  • Семантика: Если вы ожидаете, что y может быть внизу.
person ertes    schedule 30.09.2011
comment
Мне нравятся ваши комментарии о побочных эффектах. Если вы не хотите заботиться о порядке, можно определить действительно симметричный и: a &=& b = (a && b) `unamb` (b && a) - person luqui; 30.09.2011
comment
Аргументы (&&) могут иметь побочные эффекты, если они используют unsafePerformIO, но тогда вы получите то, о чем просите! - person pat; 30.09.2011
comment
@luqui Действительно, функция Data.Unamb.pand определяется именно так (по модулю нескольких уровней абстракции). - person Daniel Wagner; 30.09.2011

Haskell ленив, и, как правило, нет гарантии, какая операция будет выполнена первой.

Не совсем. Haskell чист (за исключением unsafePerformIO и реализации IO), и нет возможности наблюдать, какая операция будет выполняться первой (кроме unsafePerformIO и реализации IO). . Порядок выполнения просто не имеет значения для результата.

&& имеет таблицу истинности с 9 значениями, включая случаи, когда один или оба аргумента равны undefined, и точно определяет операцию:

a           b           a && b
True        True        True
True        False       False
True        undefined   undefined
False       True        False
False       False       False
False       undefined   False
undefined   True        undefined
undefined   False       undefined
undefined   undefined   undefined

Пока реализация следует за этой таблицей, ей разрешено выполнять действия в любом порядке.

(Если вы изучите таблицу, то заметите, что последовательная реализация не может следовать ей, если она не выполняет сначала a, а затем b, если a равно True. Но реализации Haskell не обязательно должны быть последовательно! Реализации всегда разрешено запускать выполнение b, когда она захочет; ваша единственная гарантия заключается в том, что, согласно таблице, результат выполнения b может повлиять на вашу программу только тогда, когда a имеет значение True. )

(Обратите внимание, что «лень» — это единственный способ написать функцию с таблицей истинности, подобной приведенной выше; в таких языках, как C или ML, все пять строк с undefined в любом аргументе будут быть принудительно иметь undefined в качестве результата, тогда как в Haskell (и в C, поскольку && встроено в язык C) одна из строк может вместо этого иметь False в качестве результата.)

person Jonathan Cast    schedule 31.08.2016

Я считаю, что это работает так, как вы ожидаете; оценить RHS, если LHS оценивается как True. Однако, если предположить, что RHS не имеет побочных эффектов, как бы вы узнали (или позаботились)?

Редактировать: я думаю, что RHS может быть undefined, и тогда вам будет все равно...

person pat    schedule 30.09.2011
comment
Это не неопределенно. Отчет Haskell дает полный список Prelude, который служит спецификацией. И совершенно нормально заботиться об этом, поскольку (&&) можно использовать для объединения простого и быстрого теста с тестом, требующим интенсивных вычислений. - person Fred Foo; 30.09.2011
comment
Я имел в виду, что RHS может быть неопределенным, и тогда вам будет небезразлично, будет ли он оцениваться - person pat; 30.09.2011