Структура Clojure вложена в другую структуру

Возможно ли иметь структуру, вложенную в структуру в Clojure? Рассмотрим следующий код:

(defstruct rect :height :width)
(defstruct color-rect :color (struct rect))

(defn 
#^{:doc "Echoes the details of the rect passed to it"}
echo-rect
[r]
  (println (:color r))
  (println (:height r))
  (println (:width r)))

(def first-rect (struct rect 1 2))
;(def c-rect1 (struct color-rect 249 first-rect)) ;form 1
;output "249 nil nil"
(def c-rect1 (struct color-rect 249 1 2)) ;form 2
;output "Too many arguments to struct constructor

(echo-rect c-rect1)

Конечно, это надуманный пример, но бывают случаи, когда я хочу разбить большую структуру данных на более мелкие подструктуры, чтобы облегчить поддержку кода. Как показывают комментарии, если я делаю форму 1, я получаю «249 nil nil», но если я делаю форму 2, я получаю «Слишком много аргументов для конструктора структуры».

Если я неправильно подхожу к этому вопросу, подскажите, что мне делать. Поиск в группе Clojure google мне ничего не дал.


Редактировать:

Я предполагаю, что я не был так ясен в постановке своего вопроса, как я думал, что был:

1.) Можно ли в Clojure вложить одну структуру в другую? (Судя снизу, это да.)

2.) Если да, то каким будет правильный синтаксис? (Опять же, судя снизу, похоже, что есть несколько способов сделать это.)

3.) Как получить значение по указанному ключу, если у вас есть структура, вложенная в другую структуру?

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


person Onorio Catenacci    schedule 16.02.2009    source источник


Ответы (5)


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

; Create the rect struct
(defstruct rect :height :width)

; Create the color-rect using all the keys from rect, with color added on
(def color-rect (apply create-struct (cons :color (keys (struct rect)))))

(defn create-color-rect 
  "A constructor function that takes a color and a rect, or a color height and width"
  ([c r] (apply struct (concat [color-rect c] (vals r))))
  ([c h w] (struct color-rect c h w)))

Вам не нужна функция echo-rect, вы можете просто оценить экземпляр карты структуры, чтобы увидеть, что в нем содержится:

user=> (def first-rect (struct rect 1 2))
#'user/first-rect
user=> first-rect
{:height 1, :width 2}
user=> (create-color-rect 249 first-rect)
{:color 249, :height 1, :width 2}
user=> (create-color-rect 249 1 2)
{:color 249, :height 1, :width 2}
person pjb3    schedule 17.02.2009
comment
Спасибо, Пол, это именно то, что я хотел знать. - person Onorio Catenacci; 18.02.2009

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

(Вы можете легко получить доступ к внутренностям произвольно вложенных хэшей/структур через ->, как немного синтаксического сахара.)

(defstruct rect :height :width)
(defstruct color-rect :rect :color)

(def cr (struct color-rect (struct rect 1 2) :blue))
;; => {:rect {:height 1, :width 2}, :color :blue}

(:color cr)           ;; => :blue
(:width (:rect cr))   ;; => 2
(-> cr :color)        ;; => :blue
(-> cr :rect :width)  ;; => 2
person Brian Carper    schedule 18.02.2009

Вложенные структуры возможны и иногда желательны. Однако похоже, что вы пытаетесь сделать что-то другое: похоже, вы пытаетесь использовать наследование типов структур, а не композицию. То есть в форме 2 вы создаете color-rect, который содержит прямоугольник, но вы пытаетесь создать экземпляр, как если бы он был прямоугольником. Форма 1 работает, потому что вы создаете c-rect1 из уже существующего прямоугольника, что является правильным способом использования композиции.

Быстрый поиск в группе Clojure или просто в Интернете должен привести вас к хорошему описанию различий между композицией и наследованием. В Clojure наследование почти всегда предпочтительнее композиции или утиного набора (снова см. Google).


Редактировать:

В ответ на ваш вопрос № 3: альтернативой использованию -> для извлечения данных во вложенных структурах, как описал Брайан Карпер в своем ответе, является вход, а также его братья и сестры assoc-in и update-in:

Например:

(def cr {:rect {:height 1, :width 2}, :color :blue})
(get-in cr [:rect :width])
;; => 2

(assoc-in cr [:rect :height] 7)
;; => {:rect {:height 7, :width 2}, :color :blue}

(update-in cr [:rect :width] * 2)
;; => {:rect {:height 1, :width 4}, :color :blue}

(assoc-in cr [:a :new :deeply :nested :field] 123)
;; => {:a {:new {:deeply {:nested {:field 123}}}}, 
;;     :rect {:height 1, :width 2}, :color :blue}
person Nathan Kitchen    schedule 17.02.2009

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

(defstruct color-rect :color (struct rect))

Насколько я понимаю clojure-structs, это создаст структуру (в основном карту с известными ключами), которая каким-то образом имеет структуру 'rect' в качестве одного из ключей.

Мое предположение подкрепляется наблюдением, что простая оценка (struct rect) дает

{:height nil, :width nil}

Принимая во внимание, что оценка (struct color-rect) дает:

{:color nil, {:height nil, :width nil} nil}

РЕДАКТИРОВАТЬ: Что может вам помочь, так это тот факт, что структуры не ограничиваются ключами, они определены с помощью. Похоже, вы могли бы выполнить то, что пытаетесь сделать примерно так:

(def c-rect1 (struct-map color-rect :color 249 :height 1 :width 1 )) ;form 3
person Mo.    schedule 17.02.2009
comment
Это правильно: вы используете пустую структуру rect в качестве ключа для одного из значений color-rect. Было бы разумнее использовать что-то вроде :rect. - person Nathan Kitchen; 17.02.2009

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

(defmacro extendstruct [n b & k]
  `(def ~n
    (apply create-struct
      (clojure.set/union
        (keys (struct ~b))
        #{~@k}))))

Что позволит вам написать это:

(defstruct rect :width :height)
(extendstruct color-rect rect :color)

Тестирование:

(struct rect)       ; {:width nil, :height nil}
(struct color-rect) ; {:color nil, :width nil, :height nil}

Это было бы то, что вы хотели?

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

(defstructx one :a :b)
(defstructx two :c one :d)
(defstructx three :e two :f :g)
; three
(keys (struct three)) ; #{:e :c :a :b :d :f :g}
person LiamGoodacre    schedule 20.11.2011