【问题标题】:Tail recursion in SML does not present any outputSML 中的尾递归不提供任何输出
【发布时间】:2012-12-14 13:32:36
【问题描述】:

在我之前的帖子here 之后,我尝试按照建议进行操作并转换代码 使用let 进入尾递归方法。

原始代码 - 不起作用(由于在if 条件中使用val):

fun func() = 

val decimal = 0 (* the final result *)
val multiple = 0 (* keeps track of multiples, eg. In XXV, X would be a multiple *)
val current = 0 (* the digit currently being processed *)
val top = 0   (* value of the last element in the list *)
val last_add = 0 (* the last digit that wasn't a multiple, or subtraction operation *)
val last_sub = 0
val problem = 0 (* if value is 1 then there is a problem with the input *)
val myList = [1,2,3,4,5] (* the list has more values *)

while (myList <> [])    (* run while the list is not empty *)

    val current = tl(myList) (* grab the last element from the list *)
    val myList = tl(myList) (* remove the last element from the list *)
    val top = tl(myList) (* grab the value at the end of the list *)
    if ( myList <> []) andalso (current > top))
       then      

                val decimal = decimal + current - top
                val last_sub = top;
                val myList = tl(myList)
       else     
           if ( (myList = []) andalso (current = top))
              then val decimal = decimal + current
                   val multiple = multiple + 1
              else
                  if (last_sub = current)
                     then val problem = 1

                     else
                          val decimal = decimal + current
                          val multiple = 0
                          val last_add = current

并将代码作为尾递归方法:

fun calc [] = 0
    |calc [x] = x
    |calc (head::tail) = 
       let 
          val decimal = 0
          val multiple = 0
          val current = 0
          val top = 0  
          val last_add = 0
          val last_sub = 0
          val problem = 0  
          val doNothing = 0
       in      

          let
              val current = hd(rev(head::tail))  (* grab the last element *) 
              val head::tail = rev(tl(rev(head::tail)))  (* POP action - remove the last element from the list *)
              val top = hd(rev(head::tail))      (* grab the new last element after removing *)
              in 
                if (current > top) then 
                    let 
                          val decimal = decimal + current - top
                          val last_sub = top 
                          val head::tail = rev(tl(rev(head::tail)))  (* POP action - remove the last element from the list *)
                    in
                    calc(head::tail)
                    end
                else
                 if ( (head::tail = []) andalso (current = top))
                   then let 
                          val decimal = decimal + current
                          val multiple = multiple + 1
                        in 
                          calc(head::tail)
                        end
                 else 
                     if (last_sub <> current)
                       then let 
                               val decimal = decimal + current
                               val multiple = 0
                               val last_add = current
                            in 
                               calc(head::tail)
                            end
                     else
                        (* do nothing *)    
                        val doNothing = 0
               end      

       end; 

但是,当我尝试输入时:

calc([0,100,20,30,4,50]);

我明白了:

uncaught exception Bind [nonexhaustive binding failure]
  raised at: stdIn:216.13-216.50

我知道代码很难阅读而且很长,但我将不胜感激 如果有人可以向我解释如何解决它,或者帮助我找到这个输出的原因。

谢谢

【问题讨论】:

    标签: recursion programming-languages functional-programming sml smlnj


    【解决方案1】:

    您的代码存在一些问题。

    首先,您可以使用last 来获取列表的最后一个元素。有关更多信息,请参阅List documentation。但是除非你有一个非常好的理由这样做,否则简单地从列表的开头开始并在递归时从开头弹出元素会更容易和更有效。您已经在代码中使用模式匹配将第一个元素绑定到 head

    其次,除非您使用refs(您可能不想这样做),否则标准 ML 中没有变量,只有值。这意味着如果你想在调用之间携带状态,任何累加器都需要成为你的函数的参数。使用辅助函数来初始化累加器是一种常见的模式。

    第三,不要将列表与[] 进行比较以测试它是否为空,而是使用null 函数。相信我。由于微妙的类型推断问题,您将使用= 收到警告。更好的是,对函数的参数使用模式匹配或使用case 语句。模式匹配允许编译器告诉你是否已经处理了所有可能的情况。

    第四,SML 通常使用camelCase,而不是snake_case 作为变量名。这更具风格,但随着您编写更多代码和协作,您将希望符合约定。

    第五,当你对一个列表进行递归时,不要试图查看列表中的多个值。这使事情复杂化。把它当作头元素和尾列表,一切都会变得简单得多。在我的代码中,我没有在列表中保持当前状态,而是通过将其拆分为单独的参数来做到这一点。有一个基本案例,您只需从其中一个累加器返回答案,还有一个递归案例,您使用更新的累加器值和从列表中弹出的单个值进行递归。这消除了问题场景。

    我不确定这个逻辑是否正确,因为我不知道你要计算什么,但请查看这段代码,它说明了我谈到的一些事情。

    (* This is the helper function which takes accumulators as
       parameters. You shouldn't call this directly. *)
    fun calc' decimal _ _ _ _ [] =
        (* We processed everything in the list.  Just return the accumulator. *)
        decimal
      | calc' decimal multiple lastAdd lastSub current (top::tail) =
        (* This case is for when there are 1 or more elements in the list. *)
        if current > top then
            calc' (decimal + current - top) multiple lastAdd top top tail
        else if current = top then
            calc' (decimal + current) (multiple + 1) lastAdd lastSub top tail
        else
            calc' (decimal + current) 0 current lastSub top tail
    
    (* This is the function you should call. *)
    fun calc [] = 0
      | calc [_] = 0 (* Given a single-element list. *)
      | calc (x::xs) =
        (* Apply the helper with correct initial values. *)
        calc' 0 0 0 0 x xs
    

    在函数式语言中,当您想更改变量时,无需将其分配给变量,只需递归并为正确的参数指定新值。这就是您使用递归在函数式语言中编写“循环”的方式。只要你只使用尾递归,它就和你最喜欢的命令式语言中的 while 循环一样高效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-22
      • 2015-10-22
      相关资源
      最近更新 更多