Это довольно просто перевести:
module PNormalDist where
pnormaldist :: (Ord a, Floating a) => a -> Either String a
pnormaldist qn
| qn < 0 || 1 < qn = Left "Error: qn must be in [0,1]"
| qn == 0.5 = Right 0.0
| otherwise = Right $
let w3 = negate . log $ 4 * qn * (1 - qn)
b = [ 1.570796288, 0.03706987906, -0.8364353589e-3,
-0.2250947176e-3, 0.6841218299e-5, 0.5824238515e-5,
-0.104527497e-5, 0.8360937017e-7, -0.3231081277e-8,
0.3657763036e-10, 0.6936233982e-12]
w1 = sum . zipWith (*) b $ iterate (*w3) 1
in (signum $ qn - 0.5) * sqrt (w1 * w3)
Прежде всего, давайте посмотрим на рубин - он возвращает значение, но иногда он печатает сообщение об ошибке (если дан неправильный аргумент).Это не очень скромно, поэтому давайте вернем наше значение Either String a
- где мы вернем Left String
с сообщением об ошибке, если дан неправильный аргумент, и Right a
в противном случае.
Теперь мы проверяем два случая вверху:
qn < 0 || 1 < qn = Left "Error: qn must be in [0,1]"
- это условие ошибки, когда qn
выходит за пределы диапазона. qn == 0.5 = Right 0.0
- этопроверка рубина qn == 0.5 and return * 0.0
Далее мы определяем w1
в коде рубина.Но мы переопределим это несколькими строками позже, что не очень рубиново.Значение, которое мы храним в w1
в первый раз, сразу используется в определении w3
, так почему бы не пропустить его сохранение в w1
?Нам даже не нужно делать шаг qn > 0.5 and w1 = 1.0 - w1
, потому что мы используем произведение w1 * (1.0 - w1)
в определении w3.
Поэтому мы пропускаем все это и переходим прямо к определению w3 = negate . log $ 4 * qn * (1 - qn)
.
Далее следует определение b
, которое является прямым отрывом от кода ruby (синтаксис ruby для литерала массива - это синтаксис haskell для списка).
Вот самый хитрыйбит - определение предельного значения w3
.То, что делает код ruby в
w1 = b[0]
1.upto 10 do |i|
w1 += b[i] * w3**i;
end
, - это то, что называется сгибанием - сведение набора значений (хранящихся в массиве ruby) в одно значение.Мы можем перефразировать это более функционально (но все еще в рубине), используя Array#reduce
:
w1 = b.zip(0..10).reduce(0) do |accum, (bval,i)|
accum + bval * w3^i
end
Обратите внимание, как я вставил b[0]
в цикл, используя идентификатор b[0] == b[0] * w3^0
.
Теперь мы можем перенести это напрямую на haskell, но это немного уродливо
w1 = foldl 0 (\accum (bval,i) -> accum + bval * w3**i) $ zip b [0..10]
Вместо этого я разбил его на несколько этапов - во-первых, нам действительно не нужно i
, нам просто нужностепеней w3
(начиная с w3^0 == 1
), поэтому давайте вычислим их с iterate (*w3) 1
.
Затем, вместо того, чтобы объединять их в пары с элементами b, нам в конечном итоге просто нужны их продукты, поэтомумы можем сжать их в продукты каждой пары, используя zipWith (*) b
.
Теперь наша функция складывания действительно проста - нам просто нужно сложить продукты, которые мы можем сделать, используя sum
.
Наконец, мы решаем, возвращать ли плюс или минус sqrt (w1 * w3)
, в зависимости от того, больше или меньше qn
(мы уже знаем, что оно не равно).Поэтому вместо вычисления квадратного корня в двух разных местах, как в коде ruby, я рассчитал его один раз и умножил на +1
или -1
в соответствии со знаком qn - 0.5
(signum
просто возвращаетзнак значения ).