Подтвердить что ты не робот

Почему умножение только короткого замыкания с одной стороны

Я возился с fix, и после беспорядка с ним я встретил какое-то странное поведение, а именно, что 0 * undefined - *** Exception: Prelude.undefined и undefined * 0 - 0. Это также означает, что fix (0 *) составляет *** Exception: <<loop>>, а fix (* 0) - 0.

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

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

4b9b3361

Ответ 1

Ваши рассуждения верны. Существует unamb пакет, предоставляющий инструменты для такого рода параллельных вычислений, на которые вы ссылаетесь. Фактически, он предлагает Data.Unamb.pmult, который параллельно пытается проверять, является ли каждый операнд 1 или 0, и если это немедленно дает результат. Этот параллельный подход, скорее всего, будет намного медленнее в большинстве случаев для простой арифметики!

Короткое замыкание (*) происходит только в версии 7.10 GHC. Это произошло в результате изменений в реализации типа Integer в этой версии GHC. Эта лишняя лень обычно рассматривалась как ошибка производительности (поскольку она мешает анализу строгости и может даже привести к пространственным утечкам в теории), поэтому она будет удалена в GHC 8.0.

Ответ 2

Возьмем, например,

(if expensiveTest1 then 0 else 2) * (if expensiveTest2 then 0 else 2)

Вам нужно выбрать сторону для оценки. Если expensiveTest2 - бесконечный цикл, вы никогда не сможете определить, есть ли у него правая сторона 0 или нет, поэтому вы не можете сказать, следует ли коротко замыкать правую сторону, чтобы вы никогда не могли смотреть на с левой стороны. Вы не можете проверить, есть ли обе стороны 0 сразу.

Что касается того, можете ли вы полагаться на короткое замыкание, чтобы действовать определенным образом, просто имейте в виду, что undefined и error действуют точно так же, как бесконечный цикл, если вы не используете IO. Поэтому вы можете протестировать короткое замыкание и лень с помощью undefined и error. В общем случае поведение короткого замыкания зависит от функции к функции. (Существуют также разные уровни лень. undefined и Just undefined могут давать разные результаты.)

Подробнее см. .

Ответ 3

На самом деле кажется, что fix (* 0) == 0 работает только для Integer, если вы запустите fix (* 0) :: Double или fix (* 0) :: Int, вы все равно получите ***Exception <<loop>>

Это потому, что в instance Num Integer, (*) определяется как (*) = timesInteger

timesInteger определяется в Data.Integer

-- | Multiply two 'Integer's
timesInteger :: Integer -> Integer -> Integer
timesInteger _       (S# 0#) = S# 0#
timesInteger (S# 0#) _       = S# 0#
timesInteger x       (S# 1#) = x
timesInteger (S# 1#) y       = y
timesInteger x      (S# -1#) = negateInteger x
timesInteger (S# -1#) y      = negateInteger y
timesInteger (S# x#) (S# y#)
  = case mulIntMayOflo# x# y# of
    0# -> S# (x# *# y#)
    _  -> timesInt2Integer x# y#
timesInteger [email protected](S# _) y      = timesInteger y x
-- no S# as first arg from here on
timesInteger (Jp# x) (Jp# y) = Jp# (timesBigNat x y)
timesInteger (Jp# x) (Jn# y) = Jn# (timesBigNat x y)
timesInteger (Jp# x) (S# y#)
  | isTrue# (y# >=# 0#) = Jp# (timesBigNatWord x (int2Word# y#))
  | True       = Jn# (timesBigNatWord x (int2Word# (negateInt# y#)))
timesInteger (Jn# x) (Jn# y) = Jp# (timesBigNat x y)
timesInteger (Jn# x) (Jp# y) = Jn# (timesBigNat x y)
timesInteger (Jn# x) (S# y#)
  | isTrue# (y# >=# 0#) = Jn# (timesBigNatWord x (int2Word# y#))
  | True       = Jp# (timesBigNatWord x (int2Word# (negateInt# y#)))

Посмотрите на приведенный выше код, если вы запустите (* 0) x, тогда timesInteger _ (S# 0#) будет соответствовать так, что x не будет оцениваться, а если вы запустите (0 *) x, то при проверке соответствия timesInteger _ (S# 0#) x будет оцениваться и вызывать бесконечный цикл

Мы можем использовать нижеприведенный код для его проверки:

module Test where
import Data.Function(fix)

-- fix (0 ~*) == 0
-- fix (~* 0) == ***Exception<<loop>>
(~*) :: (Num a, Eq a) => a -> a -> a
0 ~* _ = 0
_ ~* 0 = 0
x ~* y = x ~* y

-- fix (0 *~) == ***Exception<<loop>>
-- fix (*~ 0) == 0
(*~) :: (Num a, Eq a) => a -> a -> a
_ *~ 0 = 0
0 *~ _ = 0
x *~ y = x *~ y

В GHCI есть что-то еще более интересное:

*Test> let x = fix (* 0) 
*Test> x 
0
*Test> x :: Double 
*** Exception: <<loop>>
*Test>