Как написать самодокументирующий код в Clojure/любой другой lisp
Я привык к Java-коду с его длинными описательными именами и множеством временных переменных, используемых только для того, чтобы дать имя некоторому возвращаемому значению. Такой код очень легко понять даже после перерыва на год.
При использовании Lisp, однако, я не могу понять такие вещи, как "то, что находится в третьей позиции этого списка" на следующий день или даже на следующий час.
Эта проблема является фатальной, потому что у меня много записей нескольких полей, а в Java вы вызываете myRecord.mass
, и ясно, что она масса, а (nth my-record 2)
не имеет смысла вообще.
Существует ли какая-либо практика, позволяющая написать самодокументированный код Lisp (а именно, Clojure) таким образом, что не может быть вопросов типа "какие данные находятся в этой позиции списка"
Или
Можно ли обойти множество небольших (~ 5 элементов) структур clojure Map
?
Заключение
Сама функция, которую я хочу, это StructMaps - карты на основе массива с общим набором ключей.
Относительно самостоятельной документации. Как и со мной в случае R, это была стандартная библиотека, которая меня смутила. Большинство встроенных функций имеют довольно не описательные имена, и это имеет два следствия:
- Вам нужно изучить некоторые основные группы имен, которые не так уж малы.
- Я всегда (даже бессознательно) стараюсь подчиняться соглашениям об именах стандартных библиотек, поэтому трудно запомнить мои собственные имена функций.
Спасибо за ваши ответы!
Ответы
Ответ 1
В записях:
Можно ли обойти множество небольших (~ 5 элементов) структур clojure Map
?
Абсолютно! Малые карты реализованы как массивы массивов внутри и действительно очень быстрые.
Фактически, малые карты - это эквивалент записей clojure. Если у вас будет много карт, разделяющих несколько ключей, вы можете рассмотреть структуры - см. Документы на defstruct
, struct
, create-struct
(т.е. "(doc defstruct)" и т.д. В REPL).
Если вы получаете последние clojure источники из GitHub, вы можете изучить deftype
вместо этого. В этом случае вас будет интересовать defprotocol
и, возможно, также reify
. Здесь страница wiki clojure на deftype
и reify
со ссылками на дальнейшие wiki-страницы, в том числе для протоколов.
В любом случае вы сможете написать (:mass my-record)
для эквивалента myRecord.mass
.
О самодокументируемом функциональном коде:
Я привык к Java-коду с его длинными описательными именами и множеством временных переменных, используемых только для того, чтобы дать имя некоторому возвращаемому значению.
Нет ничего, что могло бы помешать вам использовать описательные имена для ваших функций, и если это означает, что они делают их длинными, это тоже не проблема. Фактически, некоторые из встроенных функций и макросов Common Lisp несут несколько известных имен (multiple-value-bind
приходит мне на ум в качестве примера).
Что касается временных переменных, используемых для присвоения имени возвращаемому значению... Вы хорошо назвали функции в самозаверяющих выражениях:
(euclidean-distance [x1 y1] [x2 y2])
Ответ 2
В Common Lisp вы должны использовать структуры (= записи) и классы (= классы).
Определить класс. Класс "тело" имеет один слот "масса" и один суперкласс.
(defclass body (physical-object)
((mass :type number
:documentation "the mass of the body in kg"
:initform 0
:initarg :mass
:accessor body-mass))
(:documentation "this class represents a physical body. The body has a mass."))
Скажем, у нас есть тело, называемое землей, тогда мы можем напечатать его массу:
(print (body-mass earth))
Вы можете создать "тело":
(make-instance 'body :mass 5973600000000000000000000)
Достаточно ли это самодокументировано?
Ответ 3
Я вижу это много. Я думаю, идея состоит в том, что, поскольку списки так легко создавать, люди используют их для всего, даже для исключения классов.
(Кроме того: вы просто не видите людей, пишущих Java x = new Object[] {111,"Earth",300.145};
, а затем не понимая, какой элемент представляет что!: -)
Тем не менее, использование простых списков - очень простой способ экспериментировать. Если я нахожусь со списком более чем двух вещей в течение более чем 2 секунд, я пишу некоторые аксессоры:
(defun planet-name (p) (nth p 1))
См. также Norvig PAIP: "Всякий раз, когда вы разрабатываете сложную структуру данных, создайте соответствующую проверку согласованности".
Ответ 4
В терминах обычно написания читаемого функционального кода. Использование Functional Decomposition действительно может сделать код намного легче освоить. Например:
(defn connect-and-process []
(with-connection [db (opendb)]
(map #(store (serialize (format-item %)) db) (get-items db))))
может быть:
(defn format-for-db [item]
(serialize (format-item item)))
(defn store-item [db item]
(store item db)
(defn store-items [db items]
(map #(store-item db %) items))
(defn connect-and-process []
(with-connection [db (opendb)]
(store-items db (get-items db))))
Для функций, которые берут карту и обрабатывают ее, что приводит к разрушению в списке аргументов, может также помочь:
(defn do-stuff [[person place action]]
(f place (action person)))
может быть более понятным, чем:
(defn do-stuff [activity]
(f (nth activity 2) ((nth activity 3) (nth activity 1))))
destructuing также работает в let
:
(let [[person place action] activity]
(f place (action person)))
проверить: http://clojure.org/special_forms
в genral привязке имен к вещам и составление небольших функций одного назначения может действительно уменьшить кривую обучения восстановлению кода.
ps: я не infront от repl, поэтому, пожалуйста, не стесняйтесь редактировать мои ошибки:)