Подтвердить что ты не робот

Хорошие примеры использования макросов Clojure, которые демонстрируют преимущества языка над мейнстримом?

Я думаю об обучении Clojure, но исходя из основанного на c-синтаксисе (java, php, С#) мира императивных языков, который будет проблемой, поэтому, естественно, спрашивает себя, действительно ли это стоит? И хотя такое утверждение вопроса может быть очень субъективным и трудным для управления, есть одна особенность Clojure (и, в более общем смысле, lisps), о которой я продолжаю читать, которая должна сделать ее самым гибким языком когда-либо: макросы.

Есть ли у вас хорошие примеры использования макросов в Clojure, для целей, которые в других основных языках (рассмотрите любые из С++, PHP, Perl, Python, Groovy/Java, С#, JavaScript) потребует гораздо меньше элегантные решения/много ненужных абстракций/хаков/и т.д.

4b9b3361

Ответ 1

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

(with-out-str (somebody else code that prints to screen))

тогда вам нужно будет изменить свой код так, чтобы у вас не было доступа.

еще один отличный пример:

(with-open-file [fh (open-a-file-code ...)]
   (do (stuff assured that the file won't leak)))

весь макрос with-something-do макросов действительно добавлен в экосистему clojure.


другая сторона пресловутой макросов заключается в том, что я трачу практически все мое текущее профессиональное время clojure, используя очень большую тяжелую библиотеку, и поэтому я трачу много времени на то, что макросы не составляют хорошо и не являются первоклассными. Авторы этой библиотеки собираются в большой версии в следующей версии, чтобы сделать все доступные функции без прохождения макросов, чтобы люди вроде меня использовали их в более высоких функциях порядка, таких как map и reduce.

Макросы

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

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

Ответ 2

Я нахожу макросы довольно полезными для определения новых языковых функций. В большинстве языков вам нужно будет дождаться появления новой версии языка для получения нового синтаксиса - в Lisp вы можете просто расширить базовый язык с помощью макросов и добавить функции самостоятельно.

Например, Clojure не имеет обязательного цикла C

(defmacro for-loop [[sym init check change :as params] & steps]
  (cond
    (not (vector? params)) 
      (throw (Error. "Binding form must be a vector for for-loop"))
    (not= 4 (count params)) 
      (throw (Error. "Binding form must have exactly 4 arguments in for-loop"))
    :default
      `(loop [~sym ~init value# nil]
         (if ~check
           (let [new-value# (do [email protected])]
             (recur ~change new-value#))
           value#))))

Использование следующим образом:

(for-loop [i 0, (< i 10), (inc i)]
  (println i))

Хорошая идея добавить императивный цикл к функциональному языку - это дискуссия, которую мы, вероятно, должны избегать здесь: -)

Ответ 3

Там также есть более эзотерический прецедент для макросов, которые я иногда использую: написание сжатого, читаемого кода, который также полностью оптимизирован. Здесь тривиальный пример:

(defmacro str* [& ss] (apply str (map eval ss)))

То, что это делает, является конкатенацией строк во время компиляции (конечно, они должны быть константами времени компиляции). Регулярная функция конкатенации строк в Clojure равна str, поэтому везде, где в коде с ограниченным циклом у меня есть длинная строка, которую я хотел бы разбить на несколько строковых литералов, я просто добавляю звезду к str и изменяю время выполнения конкатенация к времени компиляции. Использование:

(str* "I want to write some very lenghty string, most often it will be a complex"
      " SQL query. I'd hate if it meant allocating the string all over every time"
      " this is executed.")

Другой, менее тривиальный пример:

(defmacro jprint [& xs] `(doto *out* [email protected](for [x xs] `(.append ~x))))

& означает, что он принимает переменное количество аргументов (varargs, variadic function). В Clojure вызов вариационной функции использует выделенную кучей коллекцию для передачи аргументов (например, в Java, которая использует массив). Это не очень оптимально, но если я использую макрос, как указано выше, то нет вызова функции. Я использую его следующим образом:

(jprint \" (json-escape item) \")

Он компилируется в три вызова PrintWriter.append (в основном развернутый цикл).

Наконец, я хотел бы показать вам еще более радикально другое. Вы можете использовать макрос, чтобы помочь вам в определении класса похожих функций, исключая количество обширных шаблонов. Возьмем этот знакомый пример: в HTTP-клиентской библиотеке нам нужна отдельная функция для каждого из методов HTTP. Каждое определение функции довольно сложно, так как оно имеет четыре перегруженных подписи. Кроме того, каждая функция включает в себя другой класс запроса из библиотеки Apache HttpClient, но все остальное точно для всех HTTP-методов. Посмотрите, сколько кода мне нужно для этого.

(defmacro- def-http-method [name]
  `(defn ~name
     ([~'url ~'headers ~'opts ~'body]
        (handle (~(symbol (str "Http" (s/capitalize name) ".")) ~'url) ~'headers ~'opts ~'body))
     ([~'url ~'headers ~'opts] (~name ~'url ~'headers ~'opts nil))
     ([~'url ~'headers] (~name ~'url ~'headers nil nil))
     ([~'url] (~name ~'url nil nil nil))))

(doseq [m ['GET 'POST 'PUT 'DELETE 'OPTIONS 'HEAD]]
  (eval `(def-http-method ~m)))

Ответ 4

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

С doto это:

(let [tmpObject (produceObject)]
 (do
   (.setBackground tmpObject GREEN)
   (.setThis tmpObject foo)
   (.setThat tmpObject bar)
   (.outputTo tmpObject objectSink)))

Становится следующим:

(doto (produceObject)
   (.setBackground GREEN)
   (.setThis foo)
   (.setThat bar)
   (.outputTo objectSink))

Важно то, что doto не является магии - вы можете (перестроить) самостоятельно, используя стандартные функции языка.

Ответ 5

Какой-то фактический код из проекта, над которым я работал - мне нужны вложенные петли for-esque, используя unboxed ints.

(defmacro dofor
  [[i begin end step & rest] & body]
  (when step
    `(let [end# (long ~end)
           step# (long ~step)
           comp# (if (< step# 0)
                   >
                   <)]
       (loop [~i ~begin]
         (when (comp# ~i end#)
           [email protected](if rest
               `((dofor ~rest [email protected]))
               body)
           (recur (unchecked-add ~i step#)))))))

Используется как

(dofor [i 2 6 2
        j i 6 1]
  (println i j))

Что печатает

2 2
2 3
2 4
2 5
4 4
4 5

Он компилируется в нечто очень близкое к необработанному loop/recur, который я изначально пишу вручную, поэтому в целом нет никакого штрафа за производительность исполнения, в отличие от эквивалентного

(doseq [i (range 2 6 2)
        j (range i 6 1)]
  (println i j))

Я думаю, что полученный код довольно выгодно сравнивается с эквивалентом java:

for (int i = 2; i < 6; i+=2) {
    for (int j = i; j < 6; j++) {
        System.out.println(i+" "+j);
    }
}

Ответ 6

Макросы являются частью Clojure, но IMHO не верят, что именно поэтому вам следует или не следует изучать Clojure. Неизбежность данных, хорошие конструкции для обработки параллельного состояния, а также то, что это язык JVM и может использовать Java-код, - три причины. Если вы не можете найти другую причину для изучения Clojure, рассмотрите тот факт, что язык функционального программирования, вероятно, должен положительно повлиять на то, как вы приближаетесь к проблемам на любом языке.

Чтобы посмотреть на макросы, я предлагаю вам начать с Clojure макросов потоковой передачи: thread-first и thread-last -> и ->> соответственно; посетите эту страницу, и многие из различных блогов, которые обсуждают Clojure.

Удачи и получайте удовольствие.