LINUX.ORG.RU

Генерация структуры для MongoDB из hash

 , ,


0

2

Для того, чтобы использовать ORM из db/mongodb требуется, написать такой код:

(define-mongo-struct post "posts"
  ([title #:required]
   [body #:required]
   [tags #:set-add #:pull]
   [comments #:push #:pull]
   [views #:inc]))

Задача - сгенерировать его по хешу (который получаю из YAML-файла). Как это сделать? Голову уже сломал, буду благодарен за подсказку

Ответ на: комментарий от turtle_bazon

Вот я и не могу понять как. Простой пример генерации кода через макрос есть под рукой?

silver-bullet-bfg ★★
() автор топика
Ответ на: комментарий от turtle_bazon

макрос поможет

Зачем макрос? Просто в функции собрать выражение.

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

который получаю из YAML-файла

А зачем ты получаешь его из yaml, а не хранишь вот прямо так, выражением?

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

Задача - сгенерировать его по хешу (который получаю из YAML-файла)

Хотите сказать что ТС пропустил YAML-файл через sha-1 и пытается по результату сгенерировать требуемый код?

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

Чего? При чём тут хеш?

Иногда «хеш» неправильно используют как сокращение для «хеш-таблицы», что вдобавок само по себе является не структурой данных, а одним из способов её реализации. Если максимально придираться, то должно быть «ассоциативный массив».

Gentooshnik ★★★★★
()
Последнее исправление: Gentooshnik (всего исправлений: 1)
Ответ на: комментарий от Gentooshnik

Пример данных, которые имею на «входе»:

(define name "points")
(define example-data
  (hash 'fields 
     (hash 
        'x (hash 'operations: "all" 'requeired "yes")
        'y (hash 'operations: "push" 'required "no")
        'views (hash 'inc "yes"))

Итогом надо получить вот такой код (по данной хеш-таблице):

(define-mongo-struct point "points"
  ([x #:set-add #:pull #:pusg]
   [y #:push]
   [views #:inc]))

Буду очень благодарен (особенно если с пояснениями как это в итоге работает).

В lisp и racket недавно начал погружаться, вот на этом макросе как-то завис.

silver-bullet-bfg ★★
() автор топика
Ответ на: комментарий от silver-bullet-bfg

Как-то так:

#lang racket
;; это заглушка, которая должна по переданному аргументу выдавать реальный хэш
(define-for-syntax (data source)
  (cons "points"
        (hash 'fields 
              (hash 
               'x (hash 'operations: "all" 'required "yes")
               'y (hash 'operations: "push" 'required "no")
               'views (hash 'inc "yes")))))

;;; возвращаем список из двух элементов
;;; первый = первый элемент пары, полученной из функции data
;;; второй = список полей для макроса
(define-for-syntax (parse data)
  (list (car data)
        (for/list ([(field-name args) (in-hash (hash-ref (cdr data) 'fields))])
          (cons field-name
                (append 
                 (case (hash-ref args 'operations: #f)
                   [("all") '(#:set-add #:pull #:push)]
                   [("push") '(#:push)]
                   [else null])
                 (case (hash-ref args 'inc #f)
                   [("yes") '(#:inc)]
                   [else null]))))))


(define-syntax (define-mongo-from-data stx)
  (syntax-case stx ()
    [(_ VAR SOURCE)
     (with-syntax ([(NAME FIELDS) (datum->syntax stx (parse (data #'SOURCE)))])
       #'(define-mongo-struct VAR NAME . FIELDS))]))
monk ★★★★★
()
Ответ на: комментарий от monk

Прошу прощения за глупость - но что нужно сделать, чтобы убрать заглушку и передавать то, что задал вне макроса? Никак не могу понять. Т.е. я присваиваю данные к переменной, передаю их в макрос и…?

silver-bullet-bfg ★★
() автор топика
Последнее исправление: silver-bullet-bfg (всего исправлений: 1)
Ответ на: комментарий от silver-bullet-bfg

передавать то, что задал вне макроса?

Задал как?

По-идее, заглушка должна быть заменена на что-то вроде (call-with-input-file (syntax->datum source) read-yaml)

Функция read-yaml тоже должна быть определена/импортирована через define-for-syntax или (require (for-syntax «my-yaml-reader.rkt»))

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

Ой, потерял первый аргумент. Тогда так

(define-for-syntax (data source)
  (define filename (syntax->datum source))
  (cons filename
        (call-with-input-file (format "~a.yaml" filename) read-yaml)))
monk ★★★★★
()
Ответ на: комментарий от silver-bullet-bfg

А через передачу определенного ранее через define - возможно?

Как ты себе это представляешь? Define выполняется при запуске программы, а define-mongo-struct нужно сделать при компиляции.

Во время компиляции доступно только то, что определено через define-for-syntax.

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

Можно определённый ранее define завернуть в отдельный модуль, а модуль подключить через (require (for-syntax …)). Тогда тоже будет работать.

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

Возникла такая проблема. Разбираю код, как работает:

(require
 (for-syntax
  "./core/src/application-server/src/calculated.rkt"))


(define-for-syntax (data colname)
  (let*
      ([config (database-config)]
       [collections (hash-ref config 'collections)]
       [string-key (syntax->datum colname)]
       [config (hash-ref collections (string->symbol string-key))]
       [fields (hash-ref config 'fields)])
    (cons string-key fields)))

(define-for-syntax (parse/build-cdr fields)
  (for/list
      ([(field-name config) (in-hash fields)])
    (cons field-name config)))

(define-for-syntax (parse data)
  (list (car data)
        (for/list ([(field-name args) (in-hash (cdr data))])
          (cons field-name (append 0)))))

(define-syntax (define/mongo-struct stx)
  (syntax-case stx ()
    [(_ VAR COLNAME)
     (with-syntax
       ([(NAME FIELDS)
         (datum->syntax stx (parse (data #'COLNAME)))])
       #'(define VAR FIELDS))]))

(define/mongo-struct t "points")

(displayln t)

Получаю ошибки:

/usr/local/share/racket/collects/racket/private/kw.rkt:1182:25: #%app: bad syntax
  in: (#%app y . 0)
...

Как такое можно полечить? З.Ы.: спасибо за подсказки огромное. Сейчас как раз разбираюсь и стараюсь модифицировать

silver-bullet-bfg ★★
() автор топика
Последнее исправление: silver-bullet-bfg (всего исправлений: 1)
Ответ на: комментарий от silver-bullet-bfg

Для тестирования убери (define/mongo-struct t «points») из в интерактивном окне введи

(syntax->datum (expand-once '(define/mongo-struct t "points")))

Увидишь, во что раскрывается макрос.

Судя по ошибке, он раскрывается во что-то вроде

(define t ((y 0) …)), а символ (точнее, связь) y не определен.

P.S. Что ты вообще хочешь, чтобы макрос создавал? Если (define-mongo-struct …), то чем мой вариант не угодил? Если (define …), то что ожидается внутри?

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

Что ты вообще хочешь, чтобы макрос создавал? Если (define-mongo-struct …), то чем мой вариант не угодил?

Так я твой сейчас и разбираю. Просто переименовал сами процедуры в то, что больше соответствует стилю общему кода. =)

Если (define …), то что ожидается внутри?

Тут заменять буду на define-mongo-struct. Пока просто так сделал для того, чтобы проверять поэтапно.

silver-bullet-bfg ★★
() автор топика
Ответ на: комментарий от silver-bullet-bfg

Тут заменять буду на define-mongo-struct. Пока просто так сделал для того, чтобы проверять поэтапно.

Ясно. Тогда проверяй или через expand-once или через Macro Stepper в DrRacket. Или макрос должен быть хотя бы корректным.

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

Ещё для отладки макроса можно использовать конструкцию вида

#'(define VAR '(FIELDS))

Тогда вне зависимости от того, что в FIELDS, макрос будет корректным и (displayln t) сработает и покажет содержимое FIELDS.

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

В итоге получаю вот что при развороте:

;   in: (define-mongo-struct t "points" (y) (x) (id #:inc))

Текущий код (с уже поменяной заглушкой):

#lang racket
(require db/mongodb)
;; Import calculated fields for macroses
;; Only if required with for-syntax it can be used
;; internal macros
(require
 (for-syntax
  "./core/src/application-server/src/calculated.rkt"))


(define-for-syntax (data colname)
  (let*
      ([config (database-config)]
       [collections (hash-ref config 'collections)]
       [string-key (syntax->datum colname)]
       [config (hash-ref collections (string->symbol string-key))]
       [fields (hash-ref config 'fields)])
    (cons string-key fields)))

(define-for-syntax (parse data)
  (list (car data)
        (for/list ([(field-name args) (in-hash (cdr data))])
          (cons field-name
                (append
                 (case (hash-ref args 'operations: #f)
                   [("all") '(#:set-add #:pull #:push)]
                   [("push") '(#:push)]
                   [("pull") '(#:pull)]
                   [("set-add") '(#:set-add)]
                   [else null])
                 (case (hash-ref args 'inc #f)
                   [("yes") '(#:inc)]
                   [else null]))))))

(provide define/mongo-struct)
(define-syntax (define/mongo-struct stx)
  (syntax-case stx ()
    [(_ VAR COLNAME)
     (with-syntax
       ([(NAME FIELDS)
         (datum->syntax stx (parse (data #'COLNAME)))])
       #'(define-mongo-struct VAR NAME . FIELDS))]))

silver-bullet-bfg ★★
() автор топика
Ответ на: комментарий от monk

Ага, вот это очень помогло. В итоге не корректно формируется список. Вот такой приходит:

 (y) (x) (id #:inc)
silver-bullet-bfg ★★
() автор топика
Ответ на: комментарий от monk

Так. Еще вопрос по твоему коду. Получаю при выполнении в REPL такой ответ:

 (define/mongo-struct t "points")
; /home/neuromantic/Projects/Racket/hvergelmir-core/test.rkt:45.9: define-mongo-struct: bad syntax
;   in: (define-mongo-struct t "points" (y #:push) (x #:set-add #:pull #:push #:required) (id #:inc #:pull))
; Context:
;  /usr/local/share/racket/collects/syntax/parse/private/runtime-report.rkt:731:0 error/report
;  /usr/local/share/racket/collects/syntax/parse/private/runtime-report.rkt:28:0 call-current-failure-handler
;  /usr/local/share/racket/collects/racket/repl.rkt:11:26

Форма записи судя по доке вот такая:

([title #:required]
   [body #:required]
   [tags #:set-add #:pull]
   [comments #:push #:pull]
   [views #:inc])

Актуальный код:

(require
 (for-syntax
  "./core/src/application-server/src/calculated.rkt"))


(define-for-syntax (data colname)
  (let*
      ([config (database-config)]
       [collections (hash-ref config 'collections)]
       [string-key (syntax->datum colname)]
       [config (hash-ref collections (string->symbol string-key))]
       [fields (hash-ref config 'fields)])
    (cons string-key fields)))

(define-for-syntax (parse data)
  (list (car data)
        (for/list ([(field-name args) (in-hash (cdr data))])
          (cons field-name
                (append
                 (case (hash-ref args 'inc #f)
                   [("yes") '(#:inc)]
                   [else null])
                 (case (hash-ref args 'operations)
                   [("push") '(#:push)]
                   [("pull") '(#:pull)]
                   [("set-add") '(#:set-add)]
                   [("all") '(#:set-add #:pull #:push)]
                   [else null])
                 (case (hash-ref args 'required #f)
                   [("yes") '(#:required)]
                   [else null]))))))

(provide define/mongo-struct)
(define-syntax (define/mongo-struct stx)
  (syntax-case stx ()
    [(_ VAR COLNAME)
     (with-syntax
       ([(NAME FIELDS)
         (datum->syntax stx (parse (data #'COLNAME)))])
       #'(define-mongo-struct VAR NAME . FIELDS))]))

Что тут может быть не так? Как понимаю FIELDS не тот тип данных?

silver-bullet-bfg ★★
() автор топика
Ответ на: комментарий от silver-bullet-bfg

У тебя в примере все поля в общих скобках.

#‘(define-mongo-struct VAR NAME . FIELDS) замени на #’(define-mongo-struct VAR NAME FIELDS)

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

Ага. Помогло. А точка - сливает получается? Теперь в repl вот такую проблему нашёл:

; t: undefined;
;  cannot reference an identifier before its definition
;   in module: top-level
; Context:
;  /usr/local/share/racket/collects/racket/repl.rkt:11:26

Когда выполняю:

(define/mongo-struct t "points")

и потом делаю вызов:

(t)

Чтобы посмотреть задана ли переменная… З.Ы.: Огромное спасибо за помощь. Скажи свои кординаты - вставлю в авторы кода, да добавлю потом тебя в проектик на гитхабе

silver-bullet-bfg ★★
() автор топика
Ответ на: комментарий от monk

Хммм… я ступил по идее должно всё быть хорошо. Так же и дефолтный код - не привязывается

silver-bullet-bfg ★★
() автор топика
Ответ на: комментарий от monk

Так. Структура в теории завелась. Но есть одно «но». Делаю вот так в repl:

(define/mongo-struct coords "coords")

Всё вроде выполняется, но когда пытаюсь вызвать

(make-coords)

Получаю:

; make-coords: undefined;
;  cannot reference an identifier before its definition
;   in module: top-level
; Context:
;  /usr/local/share/racket/collects/racket/repl.rkt:11:26

хотя по идее должен:

; application: required keyword argument not supplied
;   procedure: make-coord
;   required keyword: #:body
; Context:
;  /usr/local/share/racket/collects/racket/repl.rkt:11:26
silver-bullet-bfg ★★
() автор топика
Ответ на: комментарий от silver-bullet-bfg

Странно. Сделал вместо define-mongo-struct просто struct для тестирования, вроде работает.

А если сделать expand-once и заменить макрос на результат раскрытия, то всё работает?

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

Да, если беру результат раскрытия и потом его без цитирования вставляю - всё окнорм. Результат после первого:

'(define-mongo-struct coords "coords" ((y #:push) (x #:set-add #:pull #:push #:required) (id #:inc #:pull)))

Если прямо его в repl загнать без цитирования - всё ок.

silver-bullet-bfg ★★
() автор топика
Последнее исправление: silver-bullet-bfg (всего исправлений: 1)
Ответ на: комментарий от silver-bullet-bfg

Нашёл. Опять в библиотеке.

В mongodb\db\mongodb\orm\struct.rkt

Там в (define-syntax (define-mongo-struct stx) …) есть строка

        (format-id stx "make-~a" #'struct)]

она должна быть

        (format-id #'struct "make-~a" #'struct)]
monk ★★★★★
()
Ответ на: комментарий от monk

Поправил, заработало. Сейчас автору еще один коммит отправлю тогда… Огромное спасибо! Я уж начал извращения пытаться изобретать

silver-bullet-bfg ★★
() автор топика
Ответ на: комментарий от silver-bullet-bfg

Можно удалить всю гигиену из определения:

(define-syntax (define/mongo-struct stx)
  (syntax-case stx ()
    [(_ VAR COLNAME)
     #`#,(datum->syntax stx (list* 'define-mongo-struct #'VAR (parse (data #'COLNAME))))]))

Но внутри макросов будет работать кривовато (как в Common Lisp).

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

Спасибо. Отправил пул-реквест…

Сейчас удивительно, что либа для MongoDB так забагована (второй баг который мне мешает)… Уже хочется взять и форкнуть для проекта. Так, собственно, я поступил с vela…

silver-bullet-bfg ★★
() автор топика
Ответ на: комментарий от silver-bullet-bfg

Уже хочется взять и форкнуть для проекта.

Если автор изменения принимает, то лучше не надо. Иначе вместо одной недоделки будет две. И не факт, что тебе захочется поддерживать свой форк долгие годы.

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

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

А вот поддерживать свой форк - я как раз сейчас занимаюсь реализацией по сути генератора web-серверов для REST на основе YAML. Когда закончу - опубликую код. Как раз, ты говорил, что если OpenSource то может впишешься). Эта штука как раз часть одного интересного продукта.

silver-bullet-bfg ★★
() автор топика
Ответ на: комментарий от silver-bullet-bfg

Удобная. Но программистов на Racket мало. Студенческих лаб по разработке на Mongo нет. Вот и получается, что у либы пара десятков пользователей (если судить по звёздам на гитхабе). Из них большинство используют её как написано в инструкции, то есть на эти две ошибки наткнуться не могут (список коллекций не читают, define-mongo-struct в макрос не заворачивают).

И даже если кто наткнулся, первая ошибка легко игнорируется прямым запросом, вторая — выключением гигиены.

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

Когда закончу - опубликую код. Как раз, ты говорил, что если OpenSource то может впишешься).

Чем смогу, помогу.

Кстати, почему «когда закончу»? Всегда думал, что лучше открывать раньше. По архитектуре иногда тоже могут что-то путное посоветовать. А менять архитектуру легче, когда переделывать меньше.

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