Мультиметоды Clojure, как добавить данные?

Во-первых, я новичок в Clojure, поэтому вопрос, скорее всего, будет глупым.

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

defrecord путь сюда?

Вопрос: Можно Я использую производные от Clojure для создания иерархии типов классов дезаписи? похоже на это, но принятый ответ говорит: «Нет, возможно, используйте интерфейсы».

Ответ действительно нет? Должен ли я писать все представления данных в виде классов Java, чтобы использовать мультиметоды Clojure?

Спасибо,

Крис.

Рабочий код:

(derive ::unlit_root ::room)
(derive ::room ::thing)
(derive ::item ::thing)
(derive ::sword ::item)
(derive ::container ::thing)
(derive ::sack ::container)
(derive ::sack ::item)
(derive ::wardrobe ::furniture)
(derive ::furniture ::thing)
(derive ::wardrobe ::furniture)

(defmulti put (fn [x y z] [x y z]))
(defmethod put [::room ::thing ::thing] [x y z] "you can only put items into containers")
(defmethod put [::room ::sword ::sack] [x y z] "the sword cuts the sack")
(defmethod put [::room ::item ::container] [x y z] "ordinary success")
(defmethod put [::unlit_room ::thing ::thing] [x y z] "it's too dark, you are eaten by a grue")
(defmethod put [::room ::sack ::wardrobe] [x y z] "you win")
(defmethod put [::room ::item ::sack] [x y z] "you put it in the sack")
(defmethod put [::room ::furniture ::thing] [x y z] "it's too big to move")

Ниже показано, что я пробовал до сих пор, но я получаю сообщение об ошибке при первом derive:

ClassCastException java.lang.Class cannot be cast to clojure.lang.Named clojure.core/namespace (core.clj:1496).

(defrecord Item [name])
(defrecord Weapon [name, damage])
(defrecord Furniture [name])
(defrecord Container [name])
(defrecord Bag [name])
(derive Weapon Item)
(derive Container Item)
(derive Bag Container)
(derive Furniture Container)

(def sword (Weapon. "sword" 10))
(def apple (Item. "apple"))
(def cupboard (Furniture. "cupboard"))
(def bag (Bag. "bag"))


(defmulti putin (fn [src dst] [src dst]))
(defmethod putin [Item Container] [src dst] :success_0)

person fadedbee    schedule 16.10.2012    source источник


Ответы (3)


Неудачный ответ, как упомянули @Arthur и @noahz, заключается в том, что иерархии нельзя описать с помощью классов. Что же остается нам с мультиметодами?

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

(def sword {:type ::weapon, :name "sword", :damage 10})
(def apple {:type ::item, :name "apple"})
(def cupboard {:type ::furniture, :name "cupboard"})
(def bag {:type ::bag, :name "bag"})

(derive ::weapon ::item)
(derive ::container ::item)
(derive ::bag ::container)
(derive ::furniture ::container)

; dispatch on [type-of-src type-of-dst]
(defmulti putin (fn [src dst] [(src :type) (dst :type)]))
(defmethod putin [::item ::container] [src dst] :success_0)

(println (putin sword bag)) ; :success_0

Альтернативой, хотя и слишком сложной, является создание карты классов для ключевых слов и использование ее для поиска ключевых слов в иерархии при диспетчеризации. Опять же, я подчеркну, что вы, вероятно, можете найти что-то лучше, но вариант есть.

; used to look up the keywords associated with classes
(def class-keyword-map (atom {}))

; get the keyword associated with an instance's class
(defn class-keyword
  [instance]
  (@class-keyword-map (class instance)))

; this macro defines a record as normal
; however, after defining the record,
; it associates the record's type with
; a keyword generated by the record name
(defmacro def-adventure-record
  [clazz & body]
  `(do
     ; create the record as normal
     (defrecord ~clazz ~@body)
     ; and add the type to the keyword lookup
     (swap!
       class-keyword-map
       assoc ~clazz (keyword (str *ns*) (str '~clazz)))))

(def-adventure-record Item [name])
(def-adventure-record Weapon [name, damage])
(def-adventure-record Furniture [name])
(def-adventure-record Container [name])
(def-adventure-record Bag [name])

; we still need to use keywords,
; but at this point, they've been
; generated for us by the macro above
(derive ::Weapon ::Item)
(derive ::Container ::Item)
(derive ::Bag ::Container)
(derive ::Furniture ::Container)

(def sword (Weapon. "sword" 10))
(def apple (Item. "apple"))
(def cupboard (Furniture. "cupboard"))
(def bag (Bag. "bag"))

; this dispatch is done on the class's keywords
(defmulti putin (fn [src dst] [(class-keyword src) (class-keyword dst)]))

; again, keywords describe the multimethod
(defmethod putin [::Item ::Container] [src dst] :success_0)

(println (putin sword bag)) ; :success_0
person Beyamor    schedule 17.10.2012
comment
Спасибо, мне нравится второе решение. В этом, с моей точки зрения, и заключается весь смысл использования LISP. Вы создали макрос (def-adventure-record) для упрощения синтаксиса (в отличие от добавления каждой записи в карту class-keyword-map вручную). - person fadedbee; 17.10.2012
comment
Может ли быть решение, включающее gen-interface для создания иерархии и реализации интерфейса с похожим именем для каждой записи? - person fadedbee; 17.10.2012
comment
Извини, приятель. Я вижу путь, на который вы указываете, но я никогда не шел по нему. Если у меня будет время, я посмотрю, смогу ли я собрать что-нибудь вместе. - person Beyamor; 18.10.2012

Вы хотите перенести систему типов Java в Clojure. Это можно сделать (в соответствии с вашими предпочтениями) с помощью протоколов (см. также http://clojure.org/protocols)

Тем не менее, я рекомендую вам прочитать следующую запись в блоге: Ориентированное программирование на Clojure. Подумайте, возможно, что структуры данных, возможно, достаточно хороши (и более гибки), чем использование типов.

person noahlz    schedule 16.10.2012

Clojure предлагает как протоколы, так и мультиметоды для решения такого рода проблем. Если вы хотите использовать дезапись, я бы рекомендовал вместо этого использовать протоколы.


конкретная проблема объясняется на странице с несколькими методами:

«Вы также можете использовать класс в качестве дочернего (но не родительского, единственный способ сделать что-то дочерним для класса — через наследование Java)».

(derive java.util.Map ::collection)
(derive java.util.Collection ::collection)

Вы можете продолжать использовать isa? иерархии с

person Arthur Ulfeldt    schedule 16.10.2012