Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 序曲求幂很难理解_Haskell_Functional Programming_Haskell Prelude - Fatal编程技术网

Haskell 序曲求幂很难理解

Haskell 序曲求幂很难理解,haskell,functional-programming,haskell-prelude,Haskell,Functional Programming,Haskell Prelude,我在读哈斯克尔的前奏曲,发现它很容易理解,然后我偶然发现了指数的定义: (^)              :: (Num a, Integral b) => a -> b -> a x ^ 0            =  1 x ^ n | n > 0    =  f x (n-1) x where f _ 0 y = y f x n y = g x n  where

我在读哈斯克尔的前奏曲,发现它很容易理解,然后我偶然发现了指数的定义:

(^)              :: (Num a, Integral b) => a -> b -> a
x ^ 0            =  1
x ^ n | n > 0    =  f x (n-1) x
                         where f _ 0 y = y
                         f x n y = g x n  where
                                g x n | even n  = g (x*x) (n `quot` 2)
                                     | otherwise = f x (n-1) (x*y)
_ ^ _            = error "Prelude.^: negative exponent"
我不明白是否需要两个嵌套的
where
s

到目前为止,我所理解的是:

(^)              :: (Num a, Integral b) => a -> b -> a
基数必须是数字和指数整数,ok

x ^ 0            =  1
基本情况下,容易

g x n | even n  = g (x*x) (n `quot` 2)
      | otherwise = f x (n-1) (x*y)
平方幂。。。有点为什么需要
f
助手?为什么要给
f
g
取单字母名称?是否只是优化,我是否遗漏了一些明显的东西

 _ ^ _            = error "Prelude.^: negative exponent"
N>0之前被检查过,如果我们到达这里,N是负数,所以错误


我的实施将直接翻译为以下代码:

Function exp-by-squaring(x, n )
    if n < 0 then return exp-by-squaring(1 / x, - n );
    else if n = 0 then return 1; else if n = 1 then return x ;
    else if n is even then return exp-by-squaring(x * x, n / 2);
    else if n is odd then return x * exp-by-squaring(x * x, (n - 1) / 2).
平方函数exp(x,n)
如果n<0,则通过平方(1/x,-n)返回exp;
否则,如果n=0,则返回1;否则,如果n=1,则返回x;
否则,如果n为偶数,则通过平方(x*x,n/2)返回exp;
否则,如果n是奇数,则通过平方(x*x,(n-1)/2)返回x*exp。

维基百科的伪代码。

f
确实是一种优化。简单的方法是“自上而下”,计算
x^(n`div`2)
,然后将结果平方。这种方法的缺点是它构建了一个中间计算堆栈。
f
让这个实现做的是首先平方
x
(一次乘法),然后递归地将结果提升到缩减指数尾部。最终结果是,该函数可能完全在机器寄存器中运行
g
似乎有助于避免在指数为偶数时检查循环结束,但我不确定这是否是一个好主意。

据我所知,只要指数为偶数,就可以通过平方运算来解决幂运算

这就引出了为什么在奇数情况下需要
f
的答案-在
gx1
情况下,我们使用
f
返回结果,在其他每一个奇数情况下,我们使用
f
返回
g
例程

我想,如果你看一个例子,你会看到最好的结果:

x ^ n | n > 0 = f x (n-1) x
  where f _ 0 y = y
        f x n y = g x n
          where g x n | even n  = g (x*x) (n `quot` 2)
                      | otherwise = f x (n-1) (x*y)

2^6 = -- x = 2, n = 6, 6 > 0 thus we can use the definition
f 2 (6-1) 2 = f 2 5 2 -- (*)
            = g 2 5 -- 5 is odd we are in the "otherwise" branch
            = f 2 4 (2*2) -- note that the second '2' is still in scope from  (*)
            = f 2 4 (4) -- (**) for reasons of better readability evaluate the expressions, be aware that haskell is lazy and wouldn't do that
            = g 2 4
            = g (2*2) (4 `quot` 2) = g 4 2
            = g (4*4) (2 `quot` 2) = g 16 1
            = f 16 0 (16*4) -- note that the 4 comes from the line marked with (**)
            = f 16 0 64 -- which is the base case for f
            = 64

现在谈谈使用单字母函数名的问题——这是你必须习惯的一种方式,这是社区中大多数人的一种写作方式。它对编译器命名函数的方式没有影响——只要它们以小写字母开头

为了说明@dfeuer在说什么,请注意
f
的编写方式:

  • f
    返回一个值
  • 或者,
    f
    使用新参数调用自身
  • 因此
    f
    是尾部递归的,因此可以很容易地转换为循环

    另一方面,考虑通过平方的交替实现幂:

    -- assume n >= 0
    exp x 0 = 1
    exp x n | even n    = exp (x*x) (n `quot` 2)
            | otherwise = x * exp x (n-1)
    
    这里的问题是,在otherwise子句中,执行的最后一个操作是乘法。因此
    exp

  • 返回1
  • 用新参数调用自己
  • 使用一些新参数调用自身,并将结果乘以
    x

  • exp
    不是尾部递归,因此无法转换为循环。

    正如其他人所指出的,为了提高效率,函数是使用尾部递归编写的

    但是,请注意,可以删除最里面的
    where
    ,同时保留尾部递归,如下所示:而不是

    x ^ n | n > 0 =  f x (n-1) x
          where f _ 0 y = y
                f x n y = g x n
                  where g x n | even n  = g (x*x) (n `quot` 2)
                              | otherwise = f x (n-1) (x*y)
    
    我们可以使用

    x ^ n | n > 0 =  f x (n-1) x
          where f _ 0 y = y
                f x n y | even n    = f (x*x) (n `quot` 2) y
                        | otherwise = f x (n-1) (x*y)
    
    这也可以说更具可读性


    然而,我不知道为什么前奏曲的作者选择了他们的变体。

    如果您能告诉我们如何通过平方例程来编写指数,也许会有助于指导我们的答案,然后我们可以解释为什么前奏曲版本是这样写的。@user5402来自维基百科的伪代码补充道,ghc中使用的求幂比前奏曲中的求幂更复杂。问题是,你不需要额外的乘法。你有一个多余的乘以1的乘法。“前奏曲一也是次优的。”奥古斯都看起来哈斯克尔的优化不是一件小事。与命令式优化非常不同。它在命令式设置中也非常重要。您的示例正是我编写它的方式(我会用backtics编写
    pow
    ,但这是次要的)。请注意,还有一种将非尾部递归函数转换为尾部递归函数的标准方法,通过向函数添加附加参数。附加参数称为
    累加器
    ,因为值(某种程度上)累加到它中,以逐步构造最终结果。函数
    f
    的参数
    y
    正好扮演了这个角色。不幸的是,我没有很好的技术参考。@DominiqueDevriese但是额外的堆栈空间将是
    O(logn)
    rigth?当您可以将其作为快速循环(即尾部递归)编写时,为什么要使用额外的堆栈?这可能不值得,但是对于一个库例程,我愿意花费一些额外的精力。你说
    f
    g
    是corecorshive,也就是说,一个在需要时调用另一个,反之亦然?@Caridorc corecorshion。正如我提到的,这避免了在奇数情况下检查零。这是否真的是一个优势是另一个问题。前奏曲版本已被弃用。对于更复杂的问题:可以随意改进它,但它不能有额外的乘法。