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 ★★★★★
()
Вы не можете добавлять комментарии в эту тему: топик перемещен в архив.