How to add keyword arguments to extended functions (Base.print)?

,

I want to custom the print of my own object, but with an optional keyword argument. However when I then call my customised print using the keyword argument Julia tells me it couldn’t find it. I already know that keyword arguments can not be used for dispatching, but can keyword arguments be added to extended functions ?

using Revise
module testPrint

export Foo, print

struct Foo{T}
    x::String
    y::T
end

end

using .testPrint

a = Foo("aa",10)

print(a) # "normal" print

import Base.print, Base.println
function print(io::IO, o::Foo; what="all")
    if(what == "all")
      print("This is $(o.x)  and this is $(o.y). Correct?")
    else
      print("This is $(o.x), stop")
    end
end

print(a)                # object-specific print, fine
print(a;what="minimal") # error, keyword argument "what" not found

FIrst, you should almost never overload print — to customize printing, you should usually overload show.

Second, have you considered using an IOContext object instead? i.e.

function Base.show(io::IO, o::Foo)
    what = get(io, :what, "all")
    ...
end
println(IOContext(stdout, :what => "minimal"), o)

This has the advantage that it is composable. For example, suppose that you have an array of Foo objects — by attaching :what to the io object, you can print the array and it will pass through to the underlying show calls for the individual elements.

Third, the IOContext interface already defines some standard keywords like :compact that may be what you want.

Fourth, as explained in the custom pretty-printing manual, if you just want single-line vs. multi-line (verbose) pretty-pretting options, you should typically define a 2-argument show for the former and a 3-argument show(io, "text/plain", x) for the latter (which is then used by e.g. the REPL display).

4 Likes

To answer this question: yes, absolutely, when you define your own print function for your ::Foo type, you can add whatever arguments you want, change the argument order, add keywords, etcetera. As long as there is at least one non-keyword argument of type ::Foo, Julia can disambiguate the dispatch (and it is not type piracy).

(Normally, when overloading a function you try not to change the API too much, to aid in usability, but this is not technically required.)

You got this error because you overloaded print(io, x::Foo) but not print(x::Foo). You would need to define:

Base.print(x::Foo; kws...) = print(stdout, x; kws...)

in order to pass keyword arguments through from the 1-argument print.

But, as I said above, normally you should not overload print to customize pretty printing.

3 Likes

I am sorry. The “manual” is too terse and I am completely lost in print/show/display/IOContext/TTY…

All I need is print() to default to print the long representation of the type (a confusion matrix report in the real application) with a keyword argument to specify which parts of the report to print.

This still doesn’t work:

module testPrint

export Foo

struct Foo{T}
  x::String
  y::T
end

function Base.show(io::IO, o::Foo;what="all")
  print(io, "Foo($(o.x), $(o.y))")
end
function Base.show(io::IO, ::MIME"text/plain", o::Foo; what="all")
  if(what == "all")
    print(io,"This is $(o.x)  and this is $(o.y). Correct?")
  else
    print(io,"This is $(o.x), stop")
  end
  return nothing
end

Base.show(mimetype::MIME"text/plain", o::Foo; what="all") =  Base.show(stdout,mimetype, o; what=what)
Base.show(o::Foo; what="all") =  Base.show(stdout,o; what=what)

Base.print(o::Foo;what="all")         = show(stdout,"text/plain",o;what=what)
Base.println(o::Foo;what="all")       = begin show(stdout,"text/plain",o;what=what); print("\n") end

end
using .testPrint

a = Foo("aa",10)

print(a) # same keyword argument not found :-/ 

You overload show to change how everything in Julia (print, repr, display, …) displays instances of your type. But then you are better off using an IOContext if you want to perform additional customization in a composable way.

If you just want a custom output function that you call occasionally, but you don’t want to affect other code that tries to output instances of your type, I would give it a different name, printreport(io::IO, x::MyType; someoptions....) and then do whatever you want.

2 Likes

What I think would be useful is versions of print and println with keywords arguments, so that
print(io,x,y;k...) would be defined as print(IOContext(io,k...),x,y). This way you would just have to type
print(io,x,y;limit=true,compact=true) instead of print(IOContext(io,:limit=>true,:compact=>true),x,y)

1 Like