Меняется ли представление чисел в lisp?

Я относительно новичок в LISP и пытаюсь понять, как поведение (общего) кода LISP может быть программно изменено.

Я имею в виду простой (но полностью академический) пример, который я пытаюсь решить, где заменяются определенные числовые цифры в исходном тексте. Я знаю об использовании макросов, которые могут манипулировать s-выражениями, но хотели бы что-то, что применяется глобально. Таким образом, где бы ни появлялось число 4 (например), используется значение 5. Таким образом, (+ 4 1) будет оцениваться до 6, а 44 - до 55.

Для пояснения, это чрезвычайно глупый, упрощенный подход, где любой символ 4 обрабатывается так, как если бы программист фактически набрал 5.

В принципе, это можно распространить на каждую цифру (от 0 до 9) и присвоить им другое "значение", но я не столько заинтересован в реализации, сколько в примере какого-то преобразования такого рода.

1 ответ

Вот ужасный подход к тому, что ты хочешь. Вместо того, чтобы иметь дело со считывателем Lisp, который (по причинам, которые должны быть очевидны) на самом деле не предназначен для того, чтобы иметь дело с такого рода вещами, поскольку никто никогда не захочет делать это в реальной жизни, этот код вмешивается в ввод-вывод система, чтобы определить класс потока, который поддерживает переназначение символов, с символами по умолчанию, которые переназначаются, будучи десятичными цифрами.

Это не совсем переносимый CL: он основан на некотором поведении, которое не вошло в стандарт CL, известный как "серые потоки". Многие (возможно, все) реализации на самом деле поддерживают это предложение, но есть некоторые неизбежные различия в том, как они это делают. Существует слой совместимости, называемый trivial-gray-streams который имеет дело со всем этим.

Я не использовал этот уровень совместимости здесь: я только определил условный пакет, который работает в двух реализациях, которые я использую. Что еще более важно, я фактически не проверял, достаточно ли этот код определяет достаточное количество методов в классе, который он определяет, чтобы он действительно работал должным образом: я довольно сильно подозреваю, что это не так. Он определяет достаточно методов, чтобы он работал достаточно для этого:

> (compile-file "silly-remapping-stream" :load t)
#P"/Users/tfb/play/lisp/silly-remapping-stream.dx64fsl"
nil
nil
> (with-input-from-string (s "(print 123)")
    (let ((srs (make-instance 'silly-rewriting-stream :parent s))
      (read srs)))
(print 234)

Итак, как вы можете видеть, это приводит к тому, что все, что читается из такого потока, имеет эту функцию перезаписи цифр. Кроме того, вы можете, например, создать безумный REPL, как этот

(defun silly-rewriting-repl (&optional (stream *standard-input*))
  (let ((srs (make-instance 'silly-remapping-stream :parent stream)))
    (flet ((pread ()
             (format t "~&?? ")
             (force-output)
             (read srs nil srs)))
      (loop for f = (pread)
            while (not (eq f srs))
            do (pprint (eval f))))))

А потом

> (silly-rewriting-repl)
?? (defvar *three* 3)

*three*
?? *three*

4

и так далее.

Одна из причин того, что это не совсем правильный подход, заключается в том, что в приведенном выше REPL:

?? "a string containing 3"

"a string containing 4"

Это не было бы проблемой в версии, которая имела дело с вещами, использующими читаемый файл.

Обратите внимание, что я ожидаю, что любой язык, который поддерживает механизмы для пользователей, которые вмешиваются в операции ввода-вывода, может сделать что-то подобное, и я ожидаю, что большинство разумных современных языков позволяют это. CL (и Lisp в целом) необычен тем, что сам язык определяется в терминах

  • читатель, который принимает потоки и возвращает объекты Lisp (и чье поведение разумно настраивается);
  • и один или оба
    • вычислитель, который берет объекты Lisp (не только строки или файлы) и обрабатывает их как код, который нужно оценить,
    • компилятор, который принимает объекты Lisp (опять же, не только строки или файлы), представляющие определения, и компилирует их;
  • принтер, который знает, как печатать объекты Lisp (и который также настраивается).

(Примечание: если есть компилятор, то оценщик может быть тривиально записан в терминах этого, так что возможно иметь реализации Lisp, которые имеют только оценщик или только компилятор, или оба.)


Вот код

;;;; A silly remapping stream
;;;

;;; This uses just enough of Gray streams to define a stream which
;;; remaps digits in an amusing way.
;;;
;;; ALMOST CERTAINLY other methods need to be defined for this stream
;;; class to be legitimate.  THIS CODE IS NOT SUITABLE FOR REAL USE
;;;
;;; This code has been 'tested' (as in: I checked READ did what I
;;; thought it should) in LW 7.1.1 and the development version of CCL.
;;; Other implementations will need changes to the package definition
;;; below, or (much better) to use a compatibility layer such as
;;; trivial-gray-streams
;;; (https://github.com/trivial-gray-streams/trivial-gray-streams),
;;; which is available via Quicklisp.
;;;

(defpackage :org.tfeb.example.silly-remapping-stream
  (:use :cl
   #+LispWorks :stream
   #+CCL :ccl)
  (:export #:silly-remapping-stream))

(in-package :org.tfeb.example.silly-remapping-stream)

(defclass silly-remapping-stream (fundamental-character-input-stream)
  ((parent :initarg :parent
           :reader srm-parent
           :initform (error "no parent"))
   (map :initarg :map
        :reader srm-map
        :initform '((#\1 . #\2)
                    (#\2 . #\3)
                    (#\3 . #\4)
                    (#\4 . #\5)
                    (#\5 . #\6)
                    (#\6 . #\7)
                    (#\7 . #\8)
                    (#\8 . #\9)
                    (#\9 . #\0)))))

(defmethod stream-read-char ((stream silly-remapping-stream))
  (let ((got (stream-read-char (srm-parent stream))))
    (typecase got
      (character
       (let ((mapped (assoc got (srm-map stream))))
         (if mapped (cdr mapped) got)))
      (t got))))

(defmacro define-srm-proxy-method (gf (s &rest other-args))
  ;; just a way of defining methods which forward to the parent stream
  `(defmethod ,gf ((s silly-remapping-stream) ,@other-args)
     (,gf (srm-parent ,s) ,@other-args)))

(define-srm-proxy-method stream-unread-char (s char))
Другие вопросы по тегам