这个翻译比较简单:
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 - 它返回一个值,但有时会打印错误消息(当给出不正确的参数时)。这不是很 Haskell 风格,因此让我们的返回值为
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
- 这是 Ruby 检查 qn == 0.5 and return * 0.0
接下来,在 Ruby 代码中定义了
w1
。但是我们稍后重新定义它,这不是很符合 Ruby 的风格。我们第一次存储在
w1
中的值立即在定义
w3
时使用,所以为什么不跳过将其存储在
w1
中的步骤呢?我们甚至不需要执行
qn > 0.5 and w1 = 1.0 - w1
步骤,因为我们在定义 w3 时使用了乘积
w1 * (1.0 - w1)
。
因此,我们跳过所有这些步骤,直接移动到定义
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数组中)缩减为单个值的过程。我们可以更加功能化地重新表述这个过程(但仍然使用Ruby),使用Array#reduce
:
w1 = b.zip(0..10).reduce(0) do |accum, (bval,i)|
accum + bval * w3^i
end
注意我如何使用恒等式b [0] == b [0] * w3 ^ 0
将b [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
来完成。
最后,根据
qn
是否大于0.5(我们已经知道它不相等),我们决定返回加号或减号
sqrt (w1 * w3)
。因此,与ruby代码中在两个不同位置计算平方根不同,我只计算了一次,并根据
qn - 0.5
的符号(
signum
仅返回值的符号)乘以
+1
或
-1
。