该signum
函数是符号的通常数学定义的实现,它有条件地返回 {-1,0,1} 中的值。这是一个理论定义,因此,它没有考虑操作的计算成本或值的数据类型,因此乘以 (-1) 是改变符号的零成本理论方法。因此,它不是编程中最有用的符号处理。
该案例signum a == 0
并不是真正有用,因为您始终可以直接进行测试a == 0
,而无需额外的计算成本signum a
。至于其他两个值,我认为它们仅以 3 种一般方式使用:
您可以测试一个值是正数还是负数以有条件地启动不同的代码,如:
f x y | signum x == -1 = h x y | otherwise = g x y
或者您将某物乘以
1
或-1
在使用它之前进行操作,例如:f x y = g x (y * b) where b = signum x
或者您在操作之前添加
1
或添加某些内容,如:-1
f x y = g x (y + b) where b = signum x
Bool
在所有情况下,符号值会更好。因此,我们只需要函数将 a 分解Num
为绝对值和布尔符号,以及一个逆函数,它根据布尔条件(表示符号)改变值的符号。这个函数相当于乘以1
或乘以-1
一个数,所以我们将其定义为类似于 的运算符(*)
。:
sgn a = a >= 0
(*.) a True = a
(*.) a _ = -a
abs a = a *. sgn a
signum1 a = 1 *. sgn a
我添加了一个signum
只能返回“{-1,1}”的二分变体。请注意,在它前面加上signum 0 = 0
我们会得到通常的signum
功能,但第三种情况是我认为通常没有用的。
我们可以类似地编写加法运算符,因为添加1
或-1
取决于某事物的符号是非常常见的情况(您可以看到这些运算符只是将True
as1
和False
as 视为-1
):
(+.) a b = a + 1 *. b
(-.) a b = a - 1 *. b
我们甚至可以将声明包含在一个名为 的类中Signed
,以便于使用,包括正确的签名和固定性。
这样,上面的一般示例不仅在代码上,而且在执行时间和空间上都会简化,因为我们避免了乘法((*.)
改为使用),一旦我们有了 a Bool
,我们就避免了额外的比较,我们可以从一种类型的data 并将其用于另一种类型而不需要类型转换,我们使用短类型Bool
而不是潜在的长类型 class Num
。但是我们获得了更大的灵活性,同时允许对代码和数据类型进行一些优化。
那么,我的问题是,是否存在与此处公开的三个一般用例不同的情况,即这种方法不容易涵盖的情况,当前signum
函数优于布尔符号方法的情况。更准确地说,我可以完全避免使用当前signum
函数而不损失效率或代码清晰度吗?
编辑:在 Reid Barton 评论之后,我将第一段修改为更“中性”的方式。
进度更新:在当前答案和评论的帮助下,为了简单明了,这种方法的代码得到了极大的改进。