2013-05-03 3 views
2

Я работаю над отличной книгой Let Over Lambda, и я пытаюсь перенести код Common Lisp на defunits на Clojure.Backquote without parens

Ниже генерирует макрос, который должен принять

(defn defunits-chaining [u units prev] 
    (if (some #(= u %) prev) 
    (throw (Throwable. (str u " depends on " prev)))) 
    (let [spec (first (filter #(= u (first %)) units))] 
    (if (nil? spec) 
     (throw (Throwable. (str "unknown unit " u))) 
     (let [chain (second spec)] 
     (if (list? chain) 
      (* (first chain) 
      (defunits-chaining 
       (second chain) 
       units 
       (cons u prev))) 
      chain))))) 

(defmacro defunits [quantity base-unit & units] 
    `(defmacro ~(symbol (str "unit-of-" quantity)) 
    [valu# un#] 
    `(* ~valu# 
     ~(case un# 
      ~base-unit 1 
      [email protected](map (fn [x] 
        `(~(first x)    ;; <-- PRETTY SURE IT'S THIS `(
         ~(defunits-chaining 
          (first x) 
          (cons `(~base-unit 1) 
           (partition 2 units)) 
          nil))) 
        (partition 2 units)))))) 

(defunits time s m 60 h 3600) 

и превратить его в макрос, который можно назвать как

(unit-of-time 4 h) 

и дают результаты в базовом блоке (секунд в Это дело). Я думаю, что проблема заключается в изменении Clojure/CL api в «case». "Дело" в CL выглядит следующим образом:

(case 'a (('b) 'no) (('c) 'nope) (('a) 'yes)) 

но в Clojure ...

(case 'a 'b 'no 'c 'nope 'a 'yes) 

КАК УДОБНО. Я изменил свою функцию Анон в гнездовой defmacro, но она продолжает генерировать как

(case un# 
    s 1 
    (m 60) 
    (h 3600) 

Как я могу предотвратить эти внешние круглые скобки?

ответ

4

Просто измените свой map на mapcat.

(defmacro defunits [quantity base-unit & units] 
    `(defmacro ~(symbol (str "unit-of-" quantity)) 
    [valu# un#] 
    `(* ~valu# 
     ~(case un# 
      ~base-unit 1 
      [email protected](mapcat (fn [x] ;; <----- mapcat instead of map is the only change 
         `(~(first x) 
          ~(defunits-chaining 
          (first x) 
          (cons `(~base-unit 1) 
            (partition 2 units)) 
          nil))) 
         (partition 2 units)))))) 

Разработать и реагировать на настоящий момент принято отвечать: flatten никогда, никогда, никогда не право (за редкими исключениями для использования только для экспертов). В лучшем случае это полосовая помощь, которая даст вам правильные результаты для простых входов, но не сработает на сложных входах. Вместо этого обычно имеет право использовать apply concat, чтобы сгладить список на один уровень или использовать mapcat вместо map, чтобы генерировать уже более плоский список или, возможно, что-то более активное и специализированное для особенно свернутой структуры данных. Здесь требуется всего лишь mapcat.

Я полагаю, что входы макросов defunits настолько жестко ограничены (они действительно должны быть символами и цифрами), на практике не будет ввода, который flatten приведет к неправильному выходу. Но сглаживание - это плохая привычка вступать, и в любом случае приводит к более длинному и сложному коду.

+0

+1 для идеального описания 'flatten' –

+0

Это именно то, что я искал раньше. Думаю, мне нужно кое-что сделать в стандартных функциях clojure. –

+1

Да, снова глядя на вопрос, это * правильный ответ. –

3

Вы можете объединить круглые пары (используя, скажем, apply concat) или использовать ответ амальома (лучшая идея).

Вы также могли бы построить свои обратные формы с помощью функции НомерСтарта:

(defmacro defunits [...] 
    (let [macro-name (symbol ...) 
     valsym (gensym "val__") 
     unitsym (gensym "unit__")] 
    (list `defmacro    ; using ` so the symbol gets ns-qualified 
      macro-name 
      [valsym unitsym] 
      (make-body-expression ...)))) ; define appropriately 

Я думаю, что это имеет смысл здесь, так как вы в конечном итоге unquoting почти все равно, с двумя уровнями синтаксиса цитаты; однако это во многом вопрос вкуса.

+0

Ничего себе, спасибо большое, это займет у меня день или два, чтобы разобраться. Я не знал, что вы можете построить его из seq, это действительно здорово. Это очень хорошо продумано, спасибо. – Steve

+0

Макросы - это просто соответствующие функции (с компилятором), возвращающие структуру списка. Backquote/синтаксис-цитата с unquoting - всего лишь один из возможных способов создания структуры списка. Каждый из них может быть использован (и полезен) без другого (хотя я верю в Clojure, я нахожу, что использую синтаксическую цитату вне макросов реже, чем использовать backquote в Scheme). –

+1

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

6

Если вы обернете свою карту в плоскость, она должна дать результаты, которые вы ищете.

(defmacro defunits [quantity base-unit & units] 
    `(defmacro ~(symbol (str "unit-of-" quantity)) 
    [valu# un#] 
    `(* ~valu# 
     ~(case un# 
      ~base-unit 1 
      [email protected](flatten ;; <- changed this 
       (map (fn [x] 
         `(~(first x) 
         ~(defunits-chaining 
          (first x) 
          (cons `(~base-unit 1) 
            (partition 2 units)) 
          nil))) 
        (partition 2 units))))))) 

Что происходит: В вашей первоначальной реализации, ваша карта возвращается что-то список списков, когда то, что вам нужно, это список атомов. Flatten принимает произвольно глубокий список списков и превращает его в один список значений.

Другой подхода будет использовать reduce, а не карта:

(defmacro defunits [quantity base-unit & units] 
    `(defmacro ~(symbol (str "my-unit-of-" quantity)) 
    [valu# un#] 
    `(* ~valu# 
     ~(case un# 
      ~base-unit 1 
      [email protected](reduce (fn [x y] ;; <- reduce instead of map 
         (concat x ;; <- use concat to string the values together 
           `(~(first y) 
           ~(defunits-chaining 
            (first y) 
            (cons `(~base-unit 1) 
             (partition 2 units)) 
            nil)))) 
         '() 
         (partition 2 units)))))) 

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

еще лучше (благодаря amalloy для пятнистости этого), есть mapcat:

(defmacro defunits [quantity base-unit & units] 
    `(defmacro ~(symbol (str "unit-of-" quantity)) 
    [valu# un#] 
    `(* ~valu# 
     ~(case un# 
      ~base-unit 1 
      [email protected](mapcat (fn [x] ;; <----- mapcat instead of map is the only change 
         `(~(first x) 
          ~(defunits-chaining 
          (first x) 
          (cons `(~base-unit 1) 
            (partition 2 units)) 
          nil))) 
         (partition 2 units)))))) 

Mapcat эффективно делает то же самое, как уменьшить версию, но неявно обрабатывает CONCAT для вас.

+0

Да, ~ @ (flatten (карта (fn [x] все было в порядке, я не уверен, как я пропустил это в сотнях вещей, которые я пробовал. * Head smack * – Steve

Смежные вопросы