Clojure let против мультиарности

Мышление похоже на функциональное, но в clojure, что лучше, производительнее и менее тяжело для JVM.

(defn- on-message
  ([options ch {:keys [headers delivery-tag]} ^bytes payload ^CompanyProto$Company$Builder company]
   (check-id company)
   (save company options)
   (basic/ack ch delivery-tag))
  ([options ch ^PersistentHashMap kwargs ^bytes payload]
   (on-message options
               ch
               kwargs
               payload
               (-> (CompanyProto$Company/newBuilder)
                   (.mergeFrom payload)))))

or

(defn- on-message [options ch {:keys [headers delivery-tag] ^bytes payload}]
  (let [company (-> (CompanyProto$Company/newBuilder) (.mergeFrom payload))]
    (check-id company)
    (save company options)
    (basic/ack ch delivery-tag)))

person Rafael Custódio    schedule 17.08.2017    source источник
comment
Есть ли причина, по которой вы рассматриваете первый вариант с мультиарностью? Если метод не вызывается откуда-то еще с обеими сигнатурами, второй (с привязкой let), безусловно, менее загроможден. Я полагаю, что вы также сохраняете один вызов в стеке, который кажется ненужным прыжком в мультиарности.   -  person Toni Vanhala    schedule 17.08.2017
comment
@ToniVanhala Сначала я сделал let, но друг сказал, что let это плохо и не функционально (он разработчик erlang), я новичок в функциональных языках ... другое, что он сказал, это то, что это странно, что clojure не без стека   -  person Rafael Custódio    schedule 17.08.2017
comment
Выражение let можно рассматривать как лямбда-абстракцию, применяемую к значению, поэтому оно хорошо подходит для функциональное программирование.   -  person Toni Vanhala    schedule 17.08.2017
comment
Спасибо @ToniVanhala   -  person Rafael Custódio    schedule 17.08.2017


Ответы (2)


Ответ на ваш прямой вопрос заключается в том, что при вызове функции не возникает никаких накладных расходов просто потому, что эта функция имеет несколько элементов. В Clojure все вызовы функций компилируются в вызов метода. Точный вызываемый метод зависит от количества аргументов — что касается JVM, каждая арность компилируется как отдельный метод. Определение того, какой метод вызывать, производится во время компиляции, поэтому во время выполнения нет накладных расходов.

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

@ToniVanhala Сначала я сделал let, но друг сказал, что let это плохо и не функционально (он разработчик erlang), я новичок в функциональных языках ... другое, что он сказал, это то, что это странно, что clojure не без стека

Кажется, это настоящий вопрос. Так что стоит заняться этим.

let — это просто выражение, которое позволяет нам привязывать значения к переменным. Эти привязки неизменны. Кроме того, хорошо известно, что let можно реализовать как макроабстракцию над лямбдой (или, в словаре Clojure, fn). Так что в let нет ничего такого, что делало бы его «нефункциональным».

Также нет ничего «странного» или загадочного в том, что Clojure не является бесстековым. Clojure — это язык JVM, и стек вызовов глубоко встроен в абстрактную вычислительную модель JVM. Хотя есть способы обойти это (например, стиль передачи продолжения), эти методы требуют либо отказа от глубокого взаимодействия с JVM, либо снижения производительности. Clojure — это в первую очередь прагматичный язык, и это прагматичная уступка.

Кроме того, Clojure (будучи Лиспом) на самом деле является языковой структурой, с помощью которой вы создаете язык, который хотите. Ваш язык может пойти на разные компромиссы. Например, ваш язык может быть "stackless". Или он может иметь первоклассные продолжения (отказ от ответственности: я автор pulley.cps) . Или у него может быть совершенно другая парадигма (например, логика против функциональности). Но это также может быть очень просто. С Clojure вы можете выбирать, за что платить.

Как ни странно, ваш друг прав в том, что код не работает. Однако это не из-за let. Скорее, почти все, кроме let, не работает. Например, (save company options) явно является побочной операцией. Однако это не обязательно означает, что ваш код плохой — даже самая чистая функциональная программа должна в какой-то момент взаимодействовать с реальным миром, если она хочет иметь какую-либо практическую ценность.

person Nathan Davis    schedule 19.08.2017
comment
Это полный ответ, спасибо за это, я хотел бы знать, можете ли вы показать несколько примеров, как я уже сказал, я новичок в функционале, и я действительно хочу его изучить, я не понял, почему сохранение не работает там... - person Rafael Custódio; 19.08.2017
comment
@RafaelCustódio, чистое функциональное программирование без побочных эффектов. Если подумать, то save /должен/ быть либо побочным эффектом, либо вызов совершенно лишний и его следует исключить. Причина этого проста (но не обязательно сразу очевидна) — значение, возвращаемое save, никогда не используется. Следовательно, вызов save имеет смысл только в том случае, если он выполняет какой-либо побочный эффект (например, сохраняет что-то в базе данных). (продолжение) - person Nathan Davis; 20.08.2017
comment
Опять же (и я не могу этого не подчеркнуть), я ни в коем случае не говорю, что ваш код плохой. На самом деле, я бы сказал, что это выглядит вполне разумно — вы не можете писать в базу данных без побочных эффектов. Но это означает, что ваш код технически не работает (в прямом смысле). Надеюсь, это поможет. - person Nathan Davis; 20.08.2017
comment
Спасибо @NathanDavis, я исследовал это и обнаружил, что использование agent/ref будет способом clojure, когда у него есть побочные эффекты, я должен где-то изолировать ... Я видел, как некоторые люди также используют компонент, я не знаю если это нормально, я думаю, что это... - person Rafael Custódio; 20.08.2017

Если вы разрабатываете с использованием Java-взаимодействия, установите *warn-on-reflection* в true. Если есть предупреждения, вам нужно устранить их с помощью типовых подсказок. Затем вы получаете «перфоматический и менее тяжелый для JVM» код.

P.S. Численные вычисления с изменчивостью, простыми типами JVM и непроверяемыми переполнениями — это другая история, если это ваш случай, вам тоже нужно это проверить.

person fevgenym    schedule 17.08.2017
comment
Спасибо за советы, так как я работаю с некоторыми классами Java, я это сделаю, но где? В Core.clj? - person Rafael Custódio; 17.08.2017
comment
@RafaelCustódio, (set! *warn-on-reflection* true) в исходном файле с этими функциями. Также сделайте это пространство имен с (:gen-class). - person fevgenym; 17.08.2017
comment
Вы можете написать неэффективный код без рефлексии. Простое устранение предупреждений об отражении не означает, что код будет работать хорошо. Кроме того, идея множественности и отражения совершенно не связаны. Что касается интерфейса, объявленный тип всех аргументов функции — Object. Типовые подсказки этого не меняют. - person Nathan Davis; 26.08.2017
comment
@NathanDavis, конечно, можешь. И да, множественность не связана, но для того, чтобы быть менее тяжелым для JVM при работе с классами Java, необходимо выполнить некоторые проверки. Я не говорил, что код будет наиболее производительным и наименее тяжелым. - person fevgenym; 28.08.2017