这很容易翻译:
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)
首先,让我们看看 ruby - 它返回一个值,但有时它会打印一条错误消息(当给定一个不正确的参数时)。这不是很haskellish,所以让我们的返回值是Either String a
- 如果给出不正确的参数,我们将返回Left String
带有错误消息的 a ,Right a
否则返回 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
在 ruby 代码中定义。但是我们在几行之后重新定义了它,这不是很红。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
. 红宝石代码的作用
w1 = b[0]
1.upto 10 do |i|
w1 += b[i] * w3**i;
end
是所谓的折叠 - 将一组值(存储在 ruby 数组中)减少为单个值。我们可以使用以下方法更实用地重申这一点(但仍然在 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
大于还是小于 0.5(我们已经知道它不等于)来决定返回 plus 还是 minus 。因此,我没有像在 ruby 代码中那样在两个不同的位置计算平方根,而是计算了一次,然后将其乘以+1
或-1
根据 的符号qn - 0.5
(signum
只返回值的符号)。