在Haskell

时间:2017-01-31 00:10:39

标签: haskell sign absolute-value

signum函数是通常mathematical definition of sign的实现,它有条件地返回{-1,0,1}中的值。这是一个理论定义,因此,它没有考虑操作的计算成本或值的数据类型,因此乘以(-1)是改变符号的零成本理论方法。因此,它不是最有用的编程符号处理方法。

案例signum a == 0并不实用,因为您可以直接测试a == 0,而无需计算signum a的额外费用。至于其他两个值,我认为它们仅用于三种方式:

  • 要么测试值是正数还是负数才能有条件地启动不同的代码,如:

    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值会更好。因此,我们只需要函数将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是非常常见的情况(您可以看到这些运算符只是处理{{1} } True1False):

-1

我们甚至可以将声明括在一个名为(+.) a b = a + 1 *. b (-.) a b = a - 1 *. b 的类中,以便于使用,包括正确的签名和固定性。

这样,上面的一般例子不仅可以简化代码,还可以简化执行时间和空间,因为我们避免了乘法(改为使用Signed),一旦我们得到{{{},我们就避免了额外的比较1}},我们可以从一种类型的数据中获取符号,并将其用于另一种类型,而无需进行类型转换,我们使用短类型(*.)而不是可能长类型的​​类Bool。但我们获得了更多的灵活性,同时允许对代码和数据类型进行一些优化。

那么,我的问题是,是否存在与此处公开的三个一般用例不同的情况,即此方法不容易涵盖的情况,当前Bool函数对Bool有利的情况标志方法。更确切地说,我是否可以完全避免使用当前Num函数而不会降低效率或代码清晰度?

编辑:在Reid Barton评论之后,我将第一段修改为更“中立”的方式。

进度更新:在当前答案和评论的帮助下,此方法的代码在简洁性和清晰度方面得到了极大的改进。

3 个答案:

答案 0 :(得分:10)

您假设“正面”和“负面”是唯一两个可能的迹象。但对于例如Complex Doublesignum操作返回一个具有相同“方向”但幅度为1的复数:

Data.Complex> signum (3 :+ 4)
0.6 :+ 0.8

答案 1 :(得分:4)

我使用了这样的函数,通过一系列向(正交和对角)相邻单元格的移动,将光标导航到方形网格中的目标偏移。

move :: (Int, Int) -> [(Int, Int)]
move (0, 0) = []
move (x, y) = (dx, dy) : move (x - dx, y - dy)
  where dx = signum x
        dy = signum y

答案 2 :(得分:4)

解决时间复杂性的问题:

分支不是免费的,如果你必须(在概念上)将值乘以几个不同位置的相同值的符号结果,那么拥有let s = signum x in ...或具有该绑定可能更有效。一个where - 条款。您不再需要每次都经过分支机构。另请注意in some cases, code can slow down due to branching behavior that goes against what the branch predictor expects

例如,假设你有这样的代码:

f x y z = (s * y) / (1 + (s * z))
  where
    s = signum x

效率分析通常不像您预期​​的那样明确,并且可能高度依赖于特定程序的非常具体的方面,正如我在上面链接的问题中所见,因此在优化"之前引用了" profile的建议。在我链接的问题中,执行更多指令的代码版本实际上运行更快比执行更少指令的版本(我可以在我的机器上验证这些结果,即使我在分析中包含了额外的排序指令)!