Question about overriding print-method for deftypes

1,094 views
Skip to first unread message

Mark Engelberg

unread,
Mar 21, 2010, 3:10:16 PM3/21/10
to clojure
Consider the following deftype:
(deftype Piece [#^int number #^char letter])

(def piece (Piece 1 \A))

Now, when I evaluate piece at the REPL, I want it to print:
1A
rather than
#:Piece{:number 1, :letter \A}

similarly, I would like (str piece) to yield "1A".

A while back, I was told on this list that the key to this was to override print-method, but I can't quite figure out how to do it:

(defmethod clojure.core/print-method ::Piece [piece writer] ???what goes here???)

I have tried things like:
(defmethod clojure.core/print-method ::Piece [piece writer] (do (pr (:number piece) writer) (pr (:letter piece) writer)))
but it doesn't work.

What's the trick?  Thanks,

Mark

Mark Engelberg

unread,
Mar 21, 2010, 3:10:47 PM3/21/10
to clojure

Michał Marczyk

unread,
Mar 21, 2010, 3:27:54 PM3/21/10
to clo...@googlegroups.com
On 21 March 2010 20:10, Mark Engelberg <mark.en...@gmail.com> wrote:
> I have tried things like:
> (defmethod clojure.core/print-method ::Piece [piece writer] (do (pr (:number
> piece) writer) (pr (:letter piece) writer)))
> but it doesn't work.

You need to replace pr with print-method inside the do. pr doesn't
accept the writer argument.

That'll only take are of printing, though; I'm not sure if there is a
way to override .toString on a deftype, since I believe that's a
method of the Object class and not an interface/protocol... :-(

All the best,
Michał

Fogus

unread,
Mar 21, 2010, 3:34:56 PM3/21/10
to Clojure
> (defmethod clojure.core/print-method ::Piece [piece writer] ???what goes
> here???)

(defmethod clojure.core/print-method ::Piece
[piece writer]

(.write writer (str (:number piece) (:letter piece)) 0 2))

Extending Piece to provide a str method can replace that ugly bit in
the middle.
-m

Mark Engelberg

unread,
Mar 21, 2010, 3:40:45 PM3/21/10
to clojure
Speaking of overriding methods, what am I doing wrong here:

(deftype Piece [#^int number #^char letter]
  Comparable
  (compareTo [x y]
    (let [c1 (compare (:number x) (:number y))]
      (if (zero? c1) (compare (:letter x) (:letter y)) c1))))

What other interesting things can be overridden for a deftype?

Michał Marczyk

unread,
Mar 21, 2010, 3:43:10 PM3/21/10
to clo...@googlegroups.com
On 21 March 2010 20:34, Fogus <mef...@gmail.com> wrote:
> Extending Piece to provide a str method can replace that ugly bit in
> the middle.

But how would one go about that? str calls .toString on its arguments,
which is in turn a method of Object, thus not present in any
interface, whereas deftype / extend only allow one to implement
interface or protocol methods...

Sincerely,
Michał

Michał Marczyk

unread,
Mar 21, 2010, 3:48:44 PM3/21/10
to clo...@googlegroups.com
On 21 March 2010 20:40, Mark Engelberg <mark.en...@gmail.com> wrote:
> Speaking of overriding methods, what am I doing wrong here:
> (deftype Piece [#^int number #^char letter]
>   Comparable
>   (compareTo [x y]
>     (let [c1 (compare (:number x) (:number y))]
>       (if (zero? c1) (compare (:letter x) (:letter y)) c1))))

deftype expects method definitions *not* to accept an explicit "self"
argument. You need to do something like this instead:

(deftype Piece [#^int number #^char letter]
Comparable
(compareTo

[x]
(let [c1 (compare number (:number x))]
(if (zero? c1) (compare letter (:letter x)) c1))))

Note that own fields can be accessed by name. If you do need a "self"
argument, you can create an implicit self available to all method
bodies by passing

:as some-symbol

to the deftype right after the fields vector.

Sincerely,
Michał

Mark Engelberg

unread,
Mar 21, 2010, 4:02:12 PM3/21/10
to clojure
I'm kind of surprised that *print-dup* behavior isn't automatically enabled for deftypes.  Is there a standard way to add this in for a specific deftype?

Michał Marczyk

unread,
Mar 21, 2010, 4:46:28 PM3/21/10
to clo...@googlegroups.com

print-dup is just another multimethod, so an implementation of that
can be defined. Obviously deserialisation with read will have no
chance of working in the context of code which doesn't know the
relevant deftype, but when the deftype is known, one could perhaps use
#= for it:

(read-string "#=(Piece 1 \A)")

Sincerely,
Michał

Meikel Brandmeyer

unread,
Mar 21, 2010, 6:19:47 PM3/21/10
to clo...@googlegroups.com
Hi,

On Sun, Mar 21, 2010 at 08:43:10PM +0100, Michał Marczyk wrote:

> But how would one go about that? str calls .toString on its arguments,
> which is in turn a method of Object, thus not present in any
> interface, whereas deftype / extend only allow one to implement
> interface or protocol methods...

user=> (deftype Xyz []
Object
(toString
[]
"This is a Xyz!"))
#'user/Xyz
user=> (str (Xyz))
"This is a Xyz!"

The docstring of deftype says protocol, interface or Object.

Sincerely
Meikel

Michał Marczyk

unread,
Mar 22, 2010, 12:28:37 AM3/22/10
to clo...@googlegroups.com
On 21 March 2010 23:19, Meikel Brandmeyer <m...@kotka.de> wrote:
> The docstring of deftype says protocol, interface or Object.

So it does. (My, do I feel silly now.) Thanks!

Sincerely,
Michał

Konrad Hinsen

unread,
Mar 22, 2010, 4:59:06 AM3/22/10
to clo...@googlegroups.com

One more thing to know about print-method for deftype: The deftype
macro generates a default implementation for deftype. Defining your
own replaces the default version, which in most situations is just fine.

However, there is a situation that requires more effort. Suppose you
have a family of types for which you want to implement a common print-
method. The obvious approach for any multimethod would be to declare
all those types as derived from some parent (a namespace-qualified
keyword will do, there is no need to have any implementation for a
parent type) and have a print-method implementation for the parent.
The problem is just that the type's default implementation will be
used in preference to the shared one. The solution is to remove the
default method using remove-method.

Konrad.

Reply all
Reply to author
Forward
0 new messages