2012-04-04 3 views
5

У меня есть циклический график, который я создал с помощью dosync и ref-set. Когда я передаю это println, я получаю java.lang.StackOverflowError, как и следовало ожидать, потому что он эффективно пытается напечатать бесконечно вложенную структуру.Как переопределить поведение println для ссылочных типов

Я обнаружил, что если я (str my-ref) это создает то, что выглядит как [email protected] и на самом деле не пытается пересечь структуру и печатать все, так что это решает проблему в прямом смысле, а только помогает, когда я очень осторожно о том, что я печатаю на экране. Я хотел бы иметь возможность позвонить (println my-graph), чтобы он напечатал ref как некоторый тип пользовательского текста (возможно, с участием str), а другой не-ref обычно.

В настоящее время у меня есть специальная функция печати, которая печатает каждый элемент структуры самостоятельно и полностью пропускает печать ref. (Получается, что смотреть на [email protected] на самом деле не очень полезно). Это неудобно использовать и мешает значительно выполнять случайную проверку материала на REPL, а также не позволяет инспектору Emacs смотреть на вещи, пока я нахожусь в swank.core/break debug thingy.

Одна деталь ref - это фактически значение в defstruct, которое также содержит некоторые другие материалы, которые я пытаюсь напечатать нормально.

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

  1. Выяснить extend-type и применять протокол CharSequence к моей defstruct структуре эд так что, когда речь идет через ref он работает должным образом. Это по-прежнему требует поэтапной проверки структуры и частного случая, когда дело доходит до ref, но по крайней мере оно локализует проблему для структуры, а не для чего-либо, содержащего структуру.
  2. Выясните, как переопределить протокол CharSequence, когда он встречается с ref. Это позволяет даже более локализованное поведение и позволяет мне просматривать циклический ref на REPL, даже если он не находится внутри структуры. Это мой предпочтительный вариант.
  3. Выясните, как это сделать с toString, который, как мне кажется, называется на некотором уровне, когда я делаю println. Я больше всего не знаю об этом. Довольно невежественны и другие, но я читал Joy of Clojure, и теперь я все вдохновлен.

Аналогично это решение должно применяться к print и pprint и все остальное, что обычно блевать при попытке печати циклического реф. Какую стратегию я должен использовать?

благодарит за любой ввод.

+1

fyi, вывод '' (str my-ref) '' почти наверняка является результатом вызова 'java.lang.Object # toString()' ', как описано здесь: http://docs.oracle. com/javase/7/docs/api/java/lang/Object.html # toString% 28% 29 – sw1nn

+0

Обратите внимание, что 'defstruct' был заменен' defrecord'. Кроме того, 'defstruct' не создает реальный тип, поэтому он не может участвовать в протоколах. – raek

+0

Я понимаю, что на самом деле я уже использовал 'defrecord'. Не знаю, почему мой оригинальный пост сказал 'defstruct'. – Sonicsmooth

ответ

4

Что вы хотите сделать, это создать новое пространство имен и определить собственные функции печати, которые моделируют способ клонирования объектов clojure и по умолчанию применяются методы clojure.

Подробно:

    Создание нс без учета пр-ул и печати методом. Создайте метод печати multimethod (скопируйте именно то, что находится в clojure.core) Создайте метод по умолчанию, который просто делегирует clojure.core/print-method Создайте метод для clojure.lang.Ref, который не рекурсивно печатает все

В качестве бонуса, если вы используете clojure 1.4.0, вы можете использовать тегированные литералы, чтобы также было возможно чтение на выходе. Вам нужно будет переопределить карту *data-readers* для пользовательского тега и функцию, которая возвращает объект ref. Вам также необходимо переопределить поведение строки чтения, чтобы обеспечить привязку к *data-readers*.

Я привел пример для java.io.File

(ns my.print 
    (:refer-clojure :exclude [pr-str read-string print-method])) 

(defmulti print-method (fn [x writer] 
     (class x))) 

(defmethod print-method :default [o ^java.io.Writer w] 
     (clojure.core/print-method o w)) 

(defmethod print-method java.io.File [o ^java.io.Writer w] 
     (.write w "#myprint/file \"") 
     (.write w (str o)) 
     (.write w "\"")) 

(defn pr-str [obj] 
    (let [s (java.io.StringWriter.)] 
    (print-method obj s) 
    (str s))) 

(defonce reader-map 
    (ref {'myprint/file (fn [arg] 
       (java.io.File. arg))})) 

(defmacro defdata-reader [sym args & body] 
    `(dosync 
    (alter reader-map assoc '~sym (fn ~args [email protected])))) 

(defn read-string [s] 
    (binding [*data-readers* @reader-map] 
    (clojure.core/read-string s))) 

Теперь вы можете называть как так (println (my.print/pr-str circular-data-structure))

+0

Спасибо, я смог реализовать это, за исключением того, что получил «Невозможно разрешить var: \ * data-reader \ *». После того, как я прокомментировал это, мне все еще остается вызов специальной функции, когда я знаю, что печатаю круговую ссылку. Я надеялся, что clojure автоматически распечатает мою ссылку так, как вы можете в Python, переопределив функции '__str__' или' __repr__'. – Sonicsmooth

+0

'* data-reader *' работает только в том случае, если вы используете clojure 1.4.0, также обязательно примите ответ, если он вам поможет. Вы также можете определить clojure.core/print-methods для ссылок, но затем вы изменяете глобальный метод печати, который может быть или не быть тем, что вы хотите. – bmillare

+0

Я вижу ... Я еще не пробовал 1.4.0. Между этим ответом и этой ссылкой: http://groups.google.com/group/clojure/browse_thread/thread/ef2834ec5937baec/1dba8d1753cda39c Мне удалось решить мою проблему. Благодарю. – Sonicsmooth

4

я нашел мое решение - просто создать Многометодную, который перегружает clojure.core/print-method для каждого конкретного тип и вызовите общую функцию из мультиметода. В этом случае каждый multimethod вызывает общий print-graph-element, который знает, что делать со ссылками (он просто печатает хэш ссылочного объекта, а не пытается распечатать значение ссылочного объекта). В этом случае я предполагаю, что функция отправки print-method - это только (type <thing>), поэтому он отправляет по типу, и вот как я пишу мультиметод. На самом деле я не смог найти документацию о том, как это работает диспетчер print-method, но, похоже, это ведет себя так.

Это решение позволяет мне просто ввести имя объекта, например v0 (который был навощенный путем (подпиточной вершины) в REPL и распечаткой метод вызывается с помощью РЕПЛ, предотвращая тем самым мою ошибку StackOverflow.

(defmethod clojure.core/print-method vertex [v writer] 
    (print-graph-element v)) 
(defmethod clojure.core/print-method edge [e writer] 
    (print-graph-element e)) 
1

Если вы просто хотите, чтобы иметь возможность выводить структуры данных с петлями, попробуйте установить *print-level*, чтобы ограничить глубину принтер сойдет.

user=> (def a (atom nil)) 
#'user/a 
user=> (set! *print-level* 3) 
3 
user=> (reset! a a) 
#<[email protected]: #<[email protected]: #<[email protected]: #>>> 

Существует также *print-length* вара, который может быть полезен при вы имеют дело с данными с бесконечной длиной.

Смежные вопросы