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

Как написать самодокументирующий код в Clojure/любой другой lisp

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

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

Эта проблема является фатальной, потому что у меня много записей нескольких полей, а в Java вы вызываете myRecord.mass, и ясно, что она масса, а (nth my-record 2) не имеет смысла вообще.

Существует ли какая-либо практика, позволяющая написать самодокументированный код Lisp (а именно, Clojure) таким образом, что не может быть вопросов типа "какие данные находятся в этой позиции списка"

Или

Можно ли обойти множество небольших (~ 5 элементов) структур clojure Map?

Заключение

Сама функция, которую я хочу, это StructMaps - карты на основе массива с общим набором ключей.

Относительно самостоятельной документации. Как и со мной в случае R, это была стандартная библиотека, которая меня смутила. Большинство встроенных функций имеют довольно не описательные имена, и это имеет два следствия:

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

Спасибо за ваши ответы!

4b9b3361

Ответ 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, поэтому, пожалуйста, не стесняйтесь редактировать мои ошибки:)