定义:在表达式上下文中不允许

时间:2018-11-05 16:46:41

标签: list scheme racket

我刚刚开始学习球拍。

我编写了以下过程:

#lang racket

(define split
   (lambda (list)
      (define plus-list '())
      (define minus-list '())

      (cond ((null? list) '())
            (else
               (do ([i (length list) (- i 1)])
                   ((zero? i))
                   (define l (list-ref list i))
                   (define item (last-element-on-list l))

                   (cond ((= (cdr l '+)) (set! plus-list (cons list plus-list)))
                         ((= (cdr l '-)) (set! minus-list (cons list minus-list))))
               )

               (cons plus-list minus-list)
            )
      )
   )
)

我没有在(list-ref lst i)中使用do,而是定义了一个变量l

(define (list-ref lst i) l)

但是似乎无法做到这一点,因为出现错误:

  

define:在表达式上下文中不允许使用:(define l(list-ref   首先))

但是define中有很多do

如果删除do内的所有定义,则必须编写大量代码,而且阅读和理解起来并不容易:

(define split
   (lambda (list)
      (define plus-list '())
      (define minus-list '())

      (cond ((null? list) '())
            (else
               (do ([i (length list) (- i 1)])
                   ((zero? i))

                   (cond ((= (cdr (last-element-on-list (list-ref list i)) '+)) (set! plus-list (cons (list-ref list i) plus-list)))
                         ((= (cdr (last-element-on-list (list-ref list i)) '-)) (set! minus-list (cons (list-ref list i) minus-list))))
               )

               (cons plus-list minus-list)
            )
      )
   )
)

如何在do中定义变量?

3 个答案:

答案 0 :(得分:1)

阅读your other question,我明白为什么您要编写 bounded 表达式-

…

(cond ((= (cdr (last-element-on-list (list-ref list i)) '+))
       (set! plus-list
             (cons (list-ref list i) plus-list)))

      ((= (cdr (last-element-on-list (list-ref list i)) '-))
       (set! minus-list
             (cons (list-ref list i) minus-list))))
…

您显示的输入列表是–

(define lst
  '((n 25 f +)
    (s 25 m +)
    (ll 20 no -)))

您的split正在检查l的每个元素的内容。 split超越了它的界限,现在它 only 仅适用于包含此特定结构元素的列表。与set!一起,else中缺少cond通常表示您做错了事。您也致电(cdr (last-element-of-list ...))。如果last-element-of-list返回一个原子,则cdr会在此处抛出错误。

考虑以更通用的方式设计split

(define (split proc l)
  (define (loop l true false)
    (cond ((null? l)
           (cons true false))
          ((proc (car l))
           (loop (cdr l)
                 (cons (car l) true)
                 false))
          (else
           (loop (cdr l)
                 true
                 (cons (car l) false)))))
  (loop l '() '()))

(split (lambda (x) (> x 5))
       '(1 5 3 9 7 0 8 3 2 6 4))

;; '((6 8 7 9) 4 2 3 0 3 5 1)

如果我们的列表包含不同的元素,我们仍然可以使用相同的split过程–

(split (lambda (x) (eq? '+ (cadr x)))
       '((1 +) (1 -) (2 +) (3 +) (2 -) (3 -) (4 +)))
;; '(((4 +) (3 +) (2 +) (1 +)) (3 -) (2 -) (1 -))

我认为现在开始学习延续传承风格永远不会太早。下面的return表示我们的延续,默认为cons,该过程与我们在原始实现中返回最终结果的过程相同。直观上,延续表示计算的“下一步”

(define (split proc l (return cons)) ;; `return` is our continuation
  (if (null? l)
      ;; base case: list is empty, return empty result
      (return '() '())
      ;; inductive case: at least one `x`
      (let* ((x (car l))
             (bool (proc x)))
        (split proc          ;; tail recur with our proc
               (cdr l)       ;; ... a smaller list
               (lambda (t f) ;; ... and "the next step"
                 (if bool                        ;; if `(proc x)` returned true
                     (return (cons x t)          ;; ... cons the `x` onto the `t` result
                             f)                  ;; ... and leave the `f` the same
                     (return t                   ;; otherwise leave `t` the same
                             (cons x f))))))))   ;; ... and cons the `x` onto the `f` result

如果我们运行split过程,您会注意到我们得到的输出与上述相同。乍一看,我们似乎把一个不错的程序弄得一团糟,但是这种实现有一个明显的优势。因为延续是用户可配置的,而不是cons,所以我们可以为两个列表tf决定完全不同的命运–

(split (lambda (x) (eq? '+ (cadr x)))
       '((1 +) (1 -) (2 +) (3 +) (2 -) (3 -) (4 +))
       (lambda (plus minus)
          (printf "plus: ~a, minus: ~a\n" plus minus)))
;; plus: ((1 +) (2 +) (3 +) (4 +)), minus: ((1 -) (2 -) (3 -))

请注意plusminus是如何得到各自结果的。我们不必挑选中间的cons结果。更直观地说,我们希望printf“下一步” ,但是我们只需要指定第一个参数–

(split (lambda (x) (eq? '+ (cadr x)))
       '((1 +) (1 -) (2 +) (3 +) (2 -) (3 -) (4 +))
       (curry printf "plus: ~a, minus: ~a\n"))
;; plus: ((1 +) (2 +) (3 +) (4 +)), minus: ((1 -) (2 -) (3 -))

现在,我们已经刮擦了功能样式的表面:D

答案 1 :(得分:1)

do循环不是惯用的球拍。它们是从Scheme继承的,无论出于何种原因,它们都不允许内部定义。我从未在Racket中使用过do循环,因为for的理解能力更强,并且通常更易于使用。另外,由于它们起源于球拍,而不起源于Scheme,因此它们支持您所期望的内部定义。

您可以使用split而不是for/fold来编写do函数,这具有不需要使用set!的附加优点(并且避免了二次访问时间为使用list-ref而不是遍历列表)。我不确定您的split函数应该做什么,因为即使删除了内部定义,它也不会编译,但这是您可能会尝试做的最好的猜测:

(define (split lst)
  (for/fold ([plus-lst '()]
             [minus-lst '()])
            ([l (in-list lst)])
    (define item (last l))
    (cond
      [(equal? item '+)
       (values (cons l plus-lst) minus-lst)]
      [(equal? item '-)
       (values plus-lst (cons l minus-lst))]
      [else
       (values plus-lst minus-lst)])))

除了使用for/fold代替do的明显重组之外,此代码还对您的代码进行了以下更改:

  1. 它使用last中内置的racket/list函数来获取列表的最后一个元素。

  2. 它使用equal?而不是=来比较符号,因为=是专门用于比较数字的。

  3. 它可以正确缩进事物,并在惯用的位置加上括号。

答案 2 :(得分:-1)

我使用let修复了您的代码,请阅读有关let的文档,该文档在Scheme / Racket中大量使用。我最近没有使用Scheme,所以我无法像在文档中那样对其进行解释。

简而言之,它是本地符号定义/重新定义,您只能在let正文中使用带值的符号。

关于let的简短示例

(define x 5)

(let ((x 10))
  (display x)) # => 10

(display x) # => 5

(let ((y 1))
  (display y)) # => 1

(display y) # = => (error)  y: undefined

您使用let修复的代码

(define split
(lambda (list)
 (let ((plus-list '())
        (minus-list '()))

  (cond ((null? list) '())
        (else
           (do ([i (length list) (- i 1)])
               ((zero? i))
               (let ((l (list-ref list i))
                     (item (last-element-on-list l)))

               (cond ((= (cdr l '+)) (set! plus-list (cons list plus-list)))
                     ((= (cdr l '-)) (set! minus-list (cons list minus-list))))))
           (cons plus-list minus-list))))))