Почему я не могу вызывать функции seq в последовательности, сгенерированной js-›clj?

Хотя я могу превратить простой объект js в объект clojure с помощью чего-то вроде;

(-> "{a: 2, b: 3}" js* js->clj)

Очевидно, я не могу сделать это с конкретным объектом goog.events.BrowserEvent в функции-обработчике, например:

(defn handle-click [e]
  ...
  (-> e .-evt js->clj keys) ;; <-------------
  ...

Функция применяется, но результирующий объект не отвечает на функции последовательности, такие как countили first, хотя я могу получать элементы, используя aget. Сообщение об ошибке, которое я получаю в консоли Chrome, такое:

Uncaught Error: No protocol
method ISeqable.-seq defined for type object: [object Object]

Почему это происходит? Разве js->clj не должно работать со всеми объектами?

Как я могу это исправить?

Спасибо!


person konr    schedule 16.04.2013    source источник


Ответы (2)


js->clj изменяет только то, что является объектом JavaScript (это реализовано с использованием instance? вместо isa?, и на то есть веские причины), когда вы передаете потомок js\Object, js->clj возвращает тот же объект. agetaset) работают, потому что они компилируются в синтаксис object[field-name] в JavaScript.

Вы можете расширить протокол ISeq (или любой другой протокол) до goog.events.BrowserEvent, и все функции, которые работают с ISeq, будут работать с goog.events.BrowserEvent. Существует доклад Криса Хаузера, в котором он показал, как расширить набор протоколов до Гугл карта. Я рекомендую посмотреть все выступление, но часть, имеющая отношение к вашему вопросу, начинается примерно на 14-й минуте.

person Rodrigo Taboada    schedule 21.04.2013

Во-первых, я обнаружил функции в закрытии google для получения ключей и значений объекта:

 (defn goog-hash-map [object]
   (zipmap (goog.object/getKeys object) (goog.object/getValues object)))

Затем, изучив исходный код cljs.core, я понял, что все, что мне нужно сделать, это расширить с его помощью интерфейс IEncodeClojure:

 (extend-protocol IEncodeClojure
   goog.events.BrowserEvent
   (-js->clj
    ([x {:keys [keywordize-keys] :as options}]
       (let [keyfn (if keywordize-keys keyword str)]
         (zipmap (map keyfn (gobj/getKeys x)) (gobj/getValues x))))
    ([x] (-js->cljs x {:keywordize-keys false}))))

исходный код не работает на этом объект, потому что его тип должен быть точно Object. Я пытался изменить функцию сравнения на instance?, т.е.

(instance? x js/Object) (into {} (for [k (js-keys x)]
                                      [(keyfn k) (thisfn (aget x k))]))

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

Uncaught TypeError: Expecting a function in instanceof check, 
but got function Object() { [native code] }`.
person konr    schedule 17.04.2013
comment
(instance? x js/Object) не работает, потому что первым параметром instance? должен быть класс. Вместо этого вы должны использовать (isa? x js/Object). - person Rodrigo Taboada; 07.05.2013