LINUX.ORG.RU

Необязательные аргументы с ключевыми словами в Scheme

 , ,


0

1

Лисперы, прошу помощи.
Есть задача: парсить файлы с S-выражениями и на основе их содержимого создавать структуры данных. S-выражения эти представляют собой маленький DSL следующего вида:

(define-my-struct struct-name
  (optional-keyword1 <optional-value1>)
  (optional-keyword2 <optional-value2>)
  (optional-keyword3 <optional-value3>)
  (mandatory-keyword <mandatory-value>))

Любой из optional-keywordN может отсутствовать (тогда используется значение по умолчанию), но если присутствуют несколько из них, то они всегда идут по порядку (т.е. optional-keyword2 может идти только после optional-keyword1), а mandatory-keyword всегда идёт последним.

Так вот, я хочу написать такой макрос, чтобы содержимоe файла просто скармливать eval'y. Первое пришедшее решение в лоб:

(define-syntax define-my-struct
  (syntax-rules (opt1 opt2 opt3 mandatory)
    ((_ name (opt1 oval1) (opt2 oval2) (opt3 oval3) (mandatory mval))
     (make-my-struct oval1 oval2 oval3 mval))
    ((_ name (opt1 oval1) (opt2 oval2) (mandatory mval))
     (make-my-struct oval1 oval2 30 mval))
    ((_ name (opt1 oval1) (mandatory mval))
     (make-my-struct oval1 20 30 mval))
    ((_ name (opt1 oval1) (opt3 oval3) (mandatory mval))
     (make-my-struct oval1 20 oval3 mval))
    ((_ name (opt2 oval2) (opt3 oval3) (mandatory mval))
     (make-my-struct 10 oval2 oval3 mval))
    ((_ name (opt2 oval2) (mandatory mval))
     (make-my-struct 10 oval2 30 mval))
    ((_ name (opt3 oval3) (mandatory mval))
     (make-my-struct 10 20 oval3 mval))))
Некрасиво, подвержено ошибкам, не масштабируется. Конечно, можно сделать так:
(define-syntax define-my-struct
  (syntax-rules (mandatory)
    ((_ name  ... (mandatory mval))
     (make-my-struct name ... mval))))
но я хочу валидацию опциональных ключевых слов, а в этом случае мы этого лишаемся. По форуму нашёл только это: Необязательные параметры функции в Scheme (комментарий), но немного не то.

P.S.
А может, тут и не нужен макрос вовсе? Проверять опциональные аргументы на этапе парсинга файлов и всё.


А может, тут и не нужен макрос вовсе

Нужен, иначе твои optional-keyword будут пытаться выполниться как функции.

define-syntax

Причина по которой я не люблю схему. Страдай. Как вариант, напиши функцию, которая делает что тебе надо с списком аргументов и из макроса дёргай её, передавая аргументы целиком. Т.е. нужная функция будет вызываться

(define-my-struct-foo 'struct-name '(
  (optional-keyword1 <optional-value1>)
  (optional-keyword2 <optional-value2>)
  (optional-keyword3 <optional-value3>)
  (mandatory-keyword <mandatory-value>)))
А в макросе ты просто берёшь head и rest и вызываешь эту функцию.

no-such-file ★★★★★
()

Это Cl, но на схеме должно быть почти так-же.

(setf *defaults* '((oval1 . 10)
                   (oval2 . 20)
                   (oval3 . 30)))

(defun get-from-optionals (optionals kword)
  (cond ((null optionals) nil)
        ((eq (caar optionals) kword) (cadar optionals))
        (t (get-from-optionals (cdr optionals) kword))))

(defun parse-optionals (optionals)
  (mapcar (lambda (default)
            (let ((from-optionals (get-from-optionals optionals (car default))))
              (if from-optionals
                  from-optionals
                  (cdr default))))
          *defaults*))

(defun parse-struct (struct)
  (append (parse-optionals (butlast struct))
          (cdar (last struct))))
CL-USER> (parse-struct '((oval3 99) (oval1 88) (mandatory-keyword mandatory-value)))
(88 20 99 MANDATORY-VALUE)

Указывать опциональные параметры в нужном порядке необязательно. Код можно упростить если переделать модель хранения структур.

poe
()
Ответ на: комментарий от no-such-file

Нужен, иначе твои optional-keyword будут пытаться выполниться как функции.

Не, я имел в виду вычитать S-выражение, а потом просто разбирать его как список, не совать в eval. Типа (eq? atom 'optional-keyword3) ... но это тоже по-албански как-то.

Как вариант, напиши функцию, которая делает что тебе надо с списком аргументов...

Ну да, примерно то же самое.

PamidoR
() автор топика
Ответ на: комментарий от poe

Спасибо.

Указывать опциональные параметры в нужном порядке необязательно. Код можно упростить если переделать модель хранения структур.

Переделать можно, но мне уже просто стало интересно, как решить именно исходную задачу.

PamidoR
() автор топика

Если Scheme R5RS, то

(define-syntax define-my-struct
  (syntax-rules (opt1)
    ((_ name (opt1 val) args ...) (define1 name val args ...))
    ((_ name args ...) (define1 name 10 args ...))))

(define-syntax define1
  (syntax-rules (opt2)
    ((_ name val1 (opt2 val) args ...) (define2 name val1 val args ...))
    ((_ name val1 args ...) (define2 name val1 20 args ...))))

(define-syntax define2
  (syntax-rules (opt3)
    ((_ name val1 val2 (opt3 val) args ...) (define3 name val1 val2 val args ...))
    ((_ name val1 val2 args ...) (define3 name val1 val2 30 args ...))))

(define-syntax define3
  (syntax-rules (mandatory)
    ((_ name val1 val2 val3 (mandatory mval))
     (make-my-struct val1 val2 val3 mval))))

Если что-то типа Racket, то можно красивее.

monk ★★★★★
()
Ответ на: комментарий от monk

Супер! То что нужно, спасибо.

Если Scheme R5RS

R6RS, Chez. syntax-case тут как-то может помочь в плане красивости?

PamidoR
() автор топика
Ответ на: комментарий от no-such-file

Да, благодарю, тоже додумался до этого.

PamidoR
() автор топика
Ответ на: комментарий от PamidoR
(define-syntax define-my-struct
  (lambda (stx)
    (define (extract args key)
      (syntax-case args ()
        [() #f]
        [((k v) r ...)
         (if (eq? (syntax->datum #'k) (syntax->datum key)) #'v (extract #'(r ...) key))]))    
    (syntax-case stx ()
      [(_ name (k v) ...)
       (identifier? #'name)
       (with-syntax ([opt1 (or (extract #'((k v) ...) #'opt1) 10)]
                     [opt2 (or (extract #'((k v) ...) #'opt2) 20)]
                     [opt3 (or (extract #'((k v) ...) #'opt3) 30)]
                     [mandatory (extract #'((k v) ...) #'mandatory)])
         #'(make-my-struct opt1 opt2 opt3 mandatory))])))

Порядок аргументов становится произвольный. Проверяется, что имя не пропущено (name - символ, а не список).

monk ★★★★★
()
Ответ на: комментарий от monk

Вот так красивее.

(define-syntax define-my-struct
  (lambda (stx)
    (define (extract args key)
      (syntax-case args ()
        [() #f]
        [((k v) r ...)
         (if (eq? (syntax->datum #'k) (syntax->datum key)) #'v (extract #'(r ...) key))]))    
    (syntax-case stx ()
      [(_ name (k v) ...)
       (identifier? #'name)
       (let ([e (lambda (x) (extract #'((k v) ...) x))])
         (with-syntax ([opt1 (or (e #'opt1) 10)]
                       [opt2 (or (e #'opt2) 20)]
                       [opt3 (or (e #'opt3) 30)]
                       [mandatory (e #'mandatory)])
           #'(list opt1 opt2 opt3 mandatory)))])))
monk ★★★★★
()
Ответ на: комментарий от no-such-file

А если эти opt123 произвольные?

Так они и есть произвольные. В with-syntax добавляем любые аргументы и указываем их обязательность. Или в каком смысле?

monk ★★★★★
()
Ответ на: комментарий от no-such-file

В смысле не известные заранее.

Заранее до чего? До запуска программы?

monk ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.