Common Lisp case and quoted elements

2019-05-11 06:55发布

问题:

I'm writing a dungeon crawler game in CL, and I'm having trouble with the case form.

Two things:

  • Common Lisp complains Duplicate keyform QUOTE in CASE statement
  • (make-instance 'cl-rogue:tile tile-type 'wall) should print as "#", but the object prints as " " no matter which tile-type I use.

The code:

(in-package :cl-user)

(defpackage :cl-rogue
  (:use :common-lisp)
  (:export
    :*rows*
    :*cols*
    :*levels*
    :tile
    :tile-type
    :tile-contents
    :tile-hidden
    :tile-locked
    :tile-closed
    :main))

(in-package :cl-rogue)

(defparameter *cols* 80)
(defparameter *rows* 24)
(defparameter *levels* 26)

The class:

(defclass tile ()
  ((tile-type
    :initarg :tile-type
    :accessor tile-type
    :initform 'floor
    :documentation "Type of tile")
    (tile-contents
      :initarg :tile-contents
      :accessor tile-contents
      :initform '()
      :documentation "Any items the tile holds")
    (tile-hidden
      :initarg :tile-hidden
      :accessor tile-hidden
      :initform nil
      :documentation "Whether the tile is hidden or shown")
    (tile-locked
      :initarg :tile-locked
      :accessor tile-locked
      :initform nil
      :documentation "Whether the tile is locked")
    (tile-closed
      :initarg :tile-closed
      :accessor tile-closed
      :initform nil
      :documentation "Whether the tile is open or closed")))

The print method:

(defmethod print-object ((object tile) stream)
  (with-slots (tile-type tile-contents tile-hidden tile-locked tile-closed) object
    (if tile-hidden
      (format stream " ")
      (let ((an-item (car tile-contents)))
        (if an-item
          (format stream "~a" an-item)
          (format stream (case tile-type
            ('wall "#")
            ('upstair "<")
            ('downstair ">")
            ('door (if tile-closed "+" "\\"))
            (otherwise " "))))))))

回答1:

You don't need to quote the symbols in CASE.

Symbols in CASE clauses are not evaluated.

(case tile-type
  (wall ...)
  (door ...))

WALL and DOOR are purely symbols and not evaluated as variables.

The Lisp reader reads 'fooas (quote foo).

You wrote:

(case tile-type
  ('wall ...)
  ('door ...))

Which is the equivalent of:

(case tile-type
  ((quote wall) ...)
  ((quote door) ...))

But you can't quote a symbol in CASE. You have to provide the symbols as literal constants.

If you write:

(let ((bar 'foo)
      (baz 'foo))
  (case bar
    (baz :we-have-a-foo-through-baz)
    (foo :we-really-have-a-foo)))

This returns :WE-REALLY-HAVE-A-FOO. Because CASE uses constant data, not variables.

CASE accepts a list of items. Since you have QUOTE as a symbol in more than clause, the compiler showed a warning.

As I said, there is no quoting possible, since the items are not evaluated.

As for CASE accepting a list of items in the clauses, it looks like this:

(case tile-type
  ((door wall) ...)
  ((floor window painting) ...))

For the WALL symbol, you need to make sure that it is in the right package when you create the object.

Better use a keyword symbol, such as :wall. Then you don't need to export it and there is no confusion about in which package the symbol is.

About the formatting of the code: You had a bullet list and right after it a code section. This is not rendered as you expect. I have added the text 'The code:' before the code. Then the rendering works as expected.