Does Kotlin have the equivalent of Python's `repr`, Groovy's `inspect`, and Rust's `Debug`?

All the aforementioned features:

  • Python’s repr() global function
  • Groovy’s Object#inspect() extension method
  • Rust’s std::fmt::Debug trait

provide a secondary string conversion, alternative to the primary toString()-like one, that is meant to convey unambiguously a value to a programmer, instead of presenting it to the user.

In most cases they are able to produce a string that could be copied and pasted back into source code to obtain an equivalent value. When that is not possible, at least they provide more information than a generic .toString() call.

For example:

  • strings are formatted with quotes and all escape characters needed to unambiguously represent the string
  • numbers are output as a full precision literal with type suffix
  • collections are output surrounded by the relevant constructor call and with all inner values represented recursively, with the same style of string representation

and so on.

From a cursory look I couldn’t find anything like that. If this is the case, I think it would be a worthwhile addition.

1 Like

You could use reflection to build something like that.

But why not use the debugger? It will allow you to view all fields and nested properties and collections properly. You won’t obtain “a string that could be copied and pasted back into source code”, but that is practically impossible to implement except for data classes and standard collections.

Most fields and values in Kotlin are not simple numbers and Strings.

1 Like

Assuming that when you need to retrieve such a string you know the type of the object (you don’t need polymorphism) then you could add such a construct yourself using extension functions. If you need polymorphism then you basically have a double dispatch case and have to do something different.

A debug print function is useful in so many circumstances, outside of a debugger: log files, exception messages, console and terminal applications are some of the uses. Mostly developer-facing interfaces, of course.

As I mentioned, most modern languages, including Groovy on the JVM, provide something like this out of the box, precisely because it’s useful in so many cases.

I don’t think reflection by itself is the best way to implement a debug printer. Some responsibility should be shifted to the class authors, as is done with .toString(), .hashCode() and other such “root” methods.

On the other hand, extension functions are resolved statically, so I don’t think they would fit all use cases.

When formatting objects for exception messages and log lines, the type (or some generic supertype) is statically known, so an extension function would work, more or less. Eg.:

class WidgetNotFoundException(
    val widget: Widget,
    cause: Throwable? = null
): Exception("Widget ${widget.debug()} was not found.", cause)

But generic developer-facing applications, such as consoles, REPLs, and such, may need dynamic dispatch. Moreover, even in the above example, it would be helpful to the developer reading the stacktrace to know precisely which sub-type of Widget was not found.

The way to achieve dynamic dispatch without reflection is, of course, having each class implement their own method, plus a generic fallback (that one using reflection) in the root class. That’s what Python does: by having repr() in the language design, every class author is encouraged to think about how to represent their own objects as a string in an unambiguous manner, the same as every Java and Kotlin developer is encouraged to think how to implement their own .toString()

Which is why I qualified it saying that you don’t get polymorphism with extension functions. But n some cases that may be sufficient.

I think no one mentioned yet that data classes will get you a free implementation of toString() which may be all you need… most of the time it’s only useful to inspect the contents of classes that represent some data, in which case you should probably already be using data classes.

The idea is not bad. I’m not sure how far Kotlin is to make this a concrete possibility at this point in time. In particular, the function will not be available for all classes (especially platform classes). It may be something that you can use invokeDynamic for, to attach the “right” method to a class for invocation.