【问题标题】:Unbound Variable in SchemeScheme中的未绑定变量
【发布时间】:2014-10-11 21:26:47
【问题描述】:

我知道我想做什么,但我无法到达那里。我正在寻找一些指导。我或多或少地强迫我想要做的事情,并且必须有比我尝试创建此功能的方式更好的方法。 我目前在调用 (set! nadj) 和 (set! count) 时遇到未绑定变量错误。

我正在尝试创建一个用户输入句子的功能。如果该句子的 25% 以上由形容词组成,则该函数返回 false。

这是我目前所拥有的:

(define OK
  (lambda (x)
    (cond
      ((adj? (car x))
       (set! count (+ count 1)))
      ((not (adj? (car x))       
            (set! nadj (+ nadj 1))))
      ((not (null? (OK (cdr x)))))                  
      ((null? x)
       (set! sum (+ nadj count)))
      ;;(set! div (/ count sum))
      ;;(* 100 div)
      ;;(< div 25))
      ((else  #f)))))

我想做的是为形容词的单词和不是形容词的单词做一个计数器。然后我试图把所有的词加起来,然后除以形容词的数量。然后我想将它乘以 100,如果它小于 25%,则返回 true。我不是在寻找答案,或多或少我只是想要一些指导。

这里是adj? 函数,如果您需要查看它。

(define adjectives '(black brown fast hairy hot quick red slow))
(define adj?
  (lambda(x)
   (if ( member x adjectives) #t #f)))

我确信这不是正常的 Scheme 表示法。我经常使用 C++ 和 Java 进行编程,但我很难过渡到 Scheme。

【问题讨论】:

    标签: variables functional-programming scheme lisp scope


    【解决方案1】:

    您说您的解决方案不是惯用的方案是正确的 - 我们非常努力地避免变异变量,所有这些 set! 操作都令人不悦:我们真的不需要它们。一个更惯用的解决方案是将计数器作为参数传递,如@uselpa 的回答所示。他的解决方案通过命名let 使用显式递归。

    不过,我们可以更进一步——函数式编程的真正精神是重用现有的高阶过程,并以能够解决我们问题的方式组合它们。我不知道您使用的是哪个 Scheme 解释器,但在 Racket 中,OK 过程可以简单地表达为:

    (define (OK x)         ; assuming a non-empty list
      (< (/ (count adj? x) ; count the number of adjectives
            (length x))    ; divide by the total number of words
         0.25))            ; is it less than 25%?
    

    如果您的 Scheme 解释器没有提供 count 过程,请从 SRFI-1 导入它;也很容易实现你自己的 - 再次,这符合函数式编程的精神:我们希望构建本​​身有用的通用过程,并且在其他上下文中易于重用和组合:

    (define (count pred lst)
      (let loop ((lst lst) (counter 0))
        (cond ((null? lst) counter)
              ((pred (car lst)) (loop (cdr lst) (+ 1 counter)))
              (else (loop (cdr lst) counter)))))
    

    扮演魔鬼的拥护者,只要我们首先定义变量(顺便说一下,这会导致“未绑定变量”错误),就可以使用命令式样式修复您的函数 - 例如,在前面放置一个 let循环函数:将其视为在递归开始之前发生的变量声明。另请注意,空列表情况必须首先出现,以避免访问空列表中的元素,并且不要忘记在每个步骤中推进递归。这很难看,但应该可以:

    (define (OK x) ; assuming a non-empty list
      ; declare the counters outside the function
      (let ((adj 0) (nadj 0))
        ; looping function
        (let loop ((x x))
          (cond
             ; is the list empty?
            ((null? x)
             ; is the number of adjectives less than 25%?
             (< (/ adj (+ adj nadj)) 0.25))
             ; is current element an adjective?
            ((adj? (car x))
             ; increment adj counter
             (set! adj (+ adj 1))
             ; always advance recursion
             (loop (cdr x)))
             ; is current element anything other than an adjective?
            (else       
             ; increment nadj counter
             (set! nadj (+ nadj 1))
             ; always advance recursion
             (loop (cdr x)))))))
    

    【讨论】:

    • @MipsMoreLikeWhips 您尝试的解决方案可以修复,请参阅上面的答案。即便如此,还有一种更惯用的方法来解决这个问题,另见我的第二个实现
    • 好的,再次感谢您的帮助@Óscar López。我知道我这样做的方式很丑陋。我被困在一种思维方式上。那么,在您重用高阶过程的第二个解决方案中,您可以将计数器作为参数传递吗?这也很有用。这会节省我很多时间哈哈。
    • @MipsMoreLikeWhips 付出额外的努力,记住:Scheme 不是 C++,避免 set! 并拥抱递归 - 更好的是,采用组合现有程序的风格来解决您的问题,这是正确的方法用 Lisp 编程时的思考。
    • 这是有道理的。你是对的,Scheme 是一种完全不同的语言,为了在那种语言上变得更好,我需要用那种语言思考。再次感谢@Óscar López。
    • 本地化和封装的突变很好,IMO(当然,在没有非本地出口的情况下,但在这种情况下它不是本地化和封装的)。语言has do after all,这是有充分理由的。仅仅为了避免set! 而重构代码在某些情况下会降低可读性。
    【解决方案2】:

    我不知道你是否熟悉 named let,但这在这里派上用场:

    (define (OK x)
      (let loop ((x x) (adj 0) (nadj 0)) ; named let
        (cond
          ((null? x)      (< (/ adj (+ adj nadj)) 0.25))
          ((adj? (car x)) (loop (cdr x) (+ 1 adj) nadj))
          (else           (loop (cdr x) adj (+ 1 nadj))))))
    

    这是以下等效代码的方便表示法:

    (define (OK x)
      (define (loop x adj nadj)
        (cond
          ((null? x)      (< (/ adj (+ adj nadj)) 0.25))
          ((adj? (car x)) (loop (cdr x) (+ 1 adj) nadj))
          (else           (loop (cdr x) adj (+ 1 nadj)))))
      (loop x 0 0))
    

    所以基本上我们定义了一个内部函数,而在诸如 C++ 和 Java 之类的语言中,循环变成了递归调用(为了增加混淆,递归调用的过程有时称为loop,如在我的例子中)。由于调用是在尾部位置完成的,因此这在 Scheme 中与您提到的语言中的经典循环一样有效。

    通过修改递归调用的参数来代替变量赋值,即在这种简单的情况下,您通常找不到set!过程。

    编辑使用set!的示例实现:

    (define OK
      (let ((adj 0) (nadj 0))
        (lambda (x)
          (cond
            ((null? x) (< (/ adj (+ adj nadj)) 0.25))
            (else      (if (adj? (car x))
                           (set! adj (+ 1 adj))
                           (set! nadj (+ 1 nadj)))
                       (OK (cdr x)))))))
    

    【讨论】:

    • 哦,好的!所以,let 本质上是为 adj 和 nadj 创建一个位置,然后将值放入这些位置。然后一旦调用“循环”,它就会评估调用的主体。
    • 是的。 “位置”是函数的参数。初始代码中的问题是您从未创建位置(使用define 或let),这就是您收到错误消息的原因。如果您有兴趣,我可以向您展示您应该如何编码,以便与我的代码进行比较。
    • 现在更有意义了。非常感谢!在过去的四个小时里,我一直在试图想出一个比我目前拥有的解决方案更好的解决方案,但我进展不快。
    • 太好了。是的,阅读好的、惯用的代码非常重要(对于您想学习的任何语言,但特别是如果新语言与您以前使用的语言有所不同)。因此你的书应该有很多有用的代码。
    • @MipsMoreLikeWhips 需要明确的是,这与像 C 或 Java 这样的语言并没有真正的不同(类型声明除外),在没有 @987654330 的情况下,你不能在某个地方直接写 adj++; @(或int adj=0;等)更早的地方。您必须先声明变量,然后再尝试使用它们。
    【解决方案3】:

    您不能设置未绑定的变量,即使是全局变量。变量指的是位置;设置一个在任何地方都不存在的变量是不可能的:

    (set!a 1)
    ;未绑定变量:a        ; a 尚未引用任何位置
    (定义 a)
    ;值:a
    (列表 a)
    ;未赋值变量:a     ;现在可以了,但尚未为其分配值
    (set!a 1)
    ;值:a
    (列表 a)
    ;值:(1)
    (set!a 2)
    ;值:1
    (列表 a)
    ;值:(2)

    localized 和 encapsulated 变异没有任何问题。根据定义,设置全局变量不是本地化的。

    您应该已经为您打算使用的变量创建了本地绑定(位置)。基本迭代 built-in form do 为您完成:

    (define (OK x) 
      (do ((adj 0) (nadj 0))
          ((null? x)          ; loop termination condition
             (< (/ adj (+ adj nadj))
                0.25))        ; return value form
        ; loop body
        (if (adj? (car x))  
            (set! adj (+ adj 1))
            ; else
            (set! nadj (+ nadj 1)))
        ; some other statements maybe...
        ))
    

    只是另一个有时可能会派上用场的选项。当然,最惯用的 Scheme 代码是使用命名的 let 构造。它还将迫使您重构您可能使用do 编写的意大利面条式代码。不。 :)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-10
      相关资源
      最近更新 更多