2015-05-11 1 views

ответ

4

Чтобы установить контекст вопроса для людей без Радости Clojure (книга, которую я действительно люблю кстати), макрос в вопросе:

(defmacro with-promises [[n tasks _ as] & body] 
    (when as 
    `(let [tasks# ~tasks 
      n# (count tasks#) 
      promises# (take n# (repeatedly promise))] 
     (dotimes [i# n#] 
     (dothreads! 
      (fn [] 
      (deliver (nth promises# i#) 
         ((nth tasks# i#)))))) 
     (let [~n tasks# 
      ~as promises#] 
     [email protected])))) 

и используется таким образом:

(defn run-tests [& all-tests] 
    (with-promises 
    [tests all-tests :as results] 
    (into (TestRun. 0 0 0) 
      (reduce #(merge-with + %1 %2) {} 
        (for [r results] 
        (if @r 
         {:run 1 :passed 1} 
         {:run 1 :failed 1})))))) 

и окончательный вызов для выполнения тестов, как:

(run-tests pass fail fail fail pass) 
=> #user.TestRun{:run 5, :passed 2, :failed 3} 

В конечном счете, последняя часть макроса делает назначение пусть и работает тело, так что вы в конечном итоге с

(let [tests tasks# 
     results promises#] 
    (into (TestRun. 0 0 0) 
    ;; rest of body 

В макро, ~ п unquoting стартовое обратно-тик вокруг '(let поэтому вы можете просто прочитать его как n, который является первым параметром для макроса (ну, первый параметр вектора, который является первым параметром для макроса).

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

Вы можете определить больше о макро, обернув его в (pprint (macroexpand-1 '(with-promises ..., который генерирует что-то подобное (я заменил автогенерируемые имена с чем-то простым, v1, n1, p1 и i1):

(clojure.core/let 
[v1 all-tests 
    n1 (clojure.core/count v1) 
    p1 (clojure.core/take n1 (clojure.core/repeatedly clojure.core/promise))] 
(clojure.core/dotimes 
    [i1 n1] 
    (user/dothreads! 
    (clojure.core/fn 
    [] 
    (clojure.core/deliver 
    (clojure.core/nth p1 i1) 
    ((clojure.core/nth v1 i1)))))) 
(clojure.core/let 
    [tests v1 
    results p1] 
    (into 
    (TestRun. 0 0 0) 
    ;; ... rest of main body 

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

Однако в этом примере использование (т.run-tests), переменная tests фактически не используется в теле вызова с обещаниями, только results есть, поэтому вы правы, чтобы задать вопрос, это просто не нужно.

Глядя на определение макроса, в этом случае могут быть дополнительные оптимизации, поскольку привязка к задачам #, похоже, не дает ничего лишнего, чем упаковка tasks. Сначала я задавался вопросом, было ли это о неизменности в дотах! вызова или макросображения для обеспечения закрытия вокруг использования, а не прямого использования параметра для макроса.

Я попытался изменить макрос, чтобы удалить tasks# полностью и непосредственно использовать ~tasks, который, кажется, до сих пор работает, и как «испытание» не является обязательной привязкой переменного в теле прогона-тестах, вы можете упасть как в n параметр из макроса, а часть ~n tasks# - часть окончательной передачи связывания без проблем.

На самом деле после прочтения несколько раз это наконец озарило меня, чтобы сделать весь вектор прочитанным как стандартное связывание деструктурирования.

EDIT: еще несколько пояснений по «тестам».

Это просто имя, это могут быть «foo-tests», «foo-bar», потому что в конечном итоге оно используется для определения чего-то в привязке let.

Если бы вводного испытания тела было что-то вроде:

(defn run-tests [& all-tests] 
    (with-promises 
    [foo all-tests :as results] 
    (println "foo was set to" foo) 
    (into (TestRun. 0 0 0) 
     ;; rest of body 

вы можете увидеть, как Foo (и результаты) используются только в конечном счете определить переменные (Eck - вы знаете, что я имею в виду), которые могут быть использованы в основной части вызова макроса. Тело есть все после начального вектора [foo all-tests :as results], но в исходном коде tests объявлен, но не используется.

+0

Марк, приветствует этот ответ. Особенно для ' pprint'. Не могли бы вы объяснить мне, как можно деструктурировать '[[n tasks _ as] & body]' работа (пропуская 'n', если' n' <= 'tests' не предоставляется в вызове' (с обещаниями [ тесты all-tests: как результаты] ... '? также правильно ли я понимаю, что« тесты »не определены? (извините за то, что они настолько упрямы, это практически единственное место в книге, которое остается для меня неясным: D – Max

+0

да, макрос вызывается с вектором в качестве первого параметра, а внутри определения макроса он разбивает его на части. Обратите внимание, что вызов «(с обещаниями [тесты all-tests: как результаты] ...)' so первым параметром to-promises является вектор с 4 элементами. Определение макроса разбивает это на четыре части 'tests' ->' n' и т. д. «Тесты» - это d efined, это имя-заполнитель, чтобы его можно было использовать в конечном теле. Я добавлю больше к моему ответу, чтобы объяснить. –

+0

тестов не имеет значения, это имя, которое есть все. Макрообложение происходит до «запуска» чего-либо и после его расширения, что «имя» используется в привязке let для определения переменной. Если вы посмотрите на расширение макроса в моем ответе, вы увидите, что «тесты» не вызываются, макрораспределение помещает его внутри '(let [tests ...])' и, следовательно, оно действительно –

1

tests, как представляется, является функцией, поэтому :as помещает результат (выход) работать tests на all-tests.

(редактировать :) После тщательного осмотра, with-promises макрос, как представляется, установив tests графу тестов.

Из того, что я читаю (не много о макросах знает), аргументы появляются на карту ("tests" "all-tests" ":as" "results") -> ("n" "tasks" "_" "as"), но то, что я совсем не могу получить то, что будет означать when требует значений для results («а»), когда мы должны создавать его. Во всяком случае, значение tests установлено в финале let макроса.

Этот код является far слишком умный, по моему скромному мнению. Фогус - мастер, но это не лучшая работа.

(Если я ошибаюсь, hopefully someone will be inspired.)

+0

Ну, похоже, что 'tests' разрушается как' n' in '(defmacro with-promises [[n tasks _ as] & body]' (см. Мою предоставленную ссылку на исходный код), так что похоже, что вы 're wrong ... – Max

+1

Sven, не могли бы вы дать мне больше намека здесь. Я вижу, что макрос определяет 'n #' как локальную переменную, я просто не понимаю, почему 'with-promises' производится для' тестов 'параметр вообще (он выводится из' tasks' <= 'all-tests' amount)? Правильно ли я понимаю, что' тесты' не установлены? укажите мне на часть, которую я не вижу, пожалуйста :) – Max

+0

Использование макро-локальные переменные с теми же именами, что и входные параметры, делают код кошмаром. Я сильно подозреваю, что он пытается показать, почему мутация плохая. :-) – sventechie