I want to be able to print the key-value pairs from a Dict-like object in a certain formatted way.
I see two ways to do this: Either write a my_print(dct::Dict) function that does this and use a regular Dict to store the pairs. Or create a new type MyDict that is essentially a Dict with a different name and extend Base.print with a method for this new type print(dct::MyDict). Since I will have to create some more custom types for which I want to define a print method anyway, I considered the latter.
But I still want MyDict to behave like a Dict. To achieve this, do I essentially have to copy-paste all the implementation code of Dict for MyDict, especially the constructors? Or is there a more clever way to do this? Or is this a bad idea in the first place and I should just define a plain old function?
There are a number of potential printing customizations you may be able apply without creating a new object: see, for example the docstring for IOContext. I often find customizing :typeinfo to be useful.
A second (perhaps less obvious) option is to create a custom <:IO object, and define show(io::MyIO, d::Dict) methods. This allows you to customize display without piracy; in practice you might call print(MyIO(stdout), d) to display your d::Dict, or define const mystdout = MyIO(stdout) in your startup.jl file and then you can say print(mystdout, d) in the REPL.
But if neither of these is attractive, then yes, I would create a new object just for display purposes. Sometimes a very “thin” wrapper suffices, essentially providing a tiny bit of functionality and allowing me to create a custom show method. Other times you need something much more extensive. If you create your modified Dict, consider making it a subtype of AbstractDict: you might get a lot of methods for free that way.
I probably was not very on point with my us of print: While a formatted printout in the REPL is definitely used for debugging purposes, the eventual goal is to write the contents of the object to a text file with a certain formatting. This includes some pre- and postamble. For dealing with the object in Julia, I would be most interested in retaining the interface of a Dict. I suppose that would constitute a kind of wrapper type, similar to what is discussed here.
But I suppose the underlying question for me is even more general:
Is there a consensus in the Julia ecosystem which approach is more “natural”:
define custom types to save data and add methods (potentially for already existing functions) that specifically dispatch on them, which I would consider the more OO-leaning way since it basically ties data to its methods via Type.
just save the data in standard types and write new functions that work with these standard types, which I would consider a more functional approach.
Is there a rule of thumb when to create custom types? In my example, the desired print/write function would probably work for every Dict but it would only make sense for Dicts with a specific content.