Opinion on Plots.jl -- exclamation marks

490 views
Skip to first unread message

Daniel Carrera

unread,
Apr 8, 2016, 3:44:12 AM4/8/16
to julia-users
Hello,

I was looking through the API for Plots.jl



Maybe I'm the only one, but I think all those exclamation marks are a bit extraneous and feel like syntactic noise. I have been following Plots.jl because I'm interested in plotting. My use of Julia comes down to either making plots, or post-processing data so I can make a plots. I get the idea from Plots.jl that functions that end in an exclamation mark are supposed to modify an existing plot. So you get things like:

plot!(...)  # Add another plot to an existing one.
title!(...)
xaxis!("mylabel", :log, :flip)
xlims!(...)
xticks!(...)

and so on...

This means that in actual usage, almost every line I write needs to have an extra `!`. To me this means that the `!` is not adding real information and is just syntactic noise. I currently use PyPlot, so I use that as a point of comparison. In PyPlot, all commands edit the current plot unless you explicitly call `figure()` to create a new plot. You can also use clf() to clear the current plot. I think this is something that PyPlot / Matplotlib get right. The special syntax lines up with the less common action.

I don't know if anyone agrees with me. I still think Plots.jl is a step in the right direction and I'll keep cheering from the stands. I just wanted to share my thoughts.

Cheers,
Daniel.

Tamas Papp

unread,
Apr 8, 2016, 4:00:47 AM4/8/16
to julia...@googlegroups.com
Hi,

IMO an extra ! is a small price to pay for consistency --- you are after
all modifying an existing object. Avoiding globals is also a good
strategy.

The lure of terser syntax for common use cases is always there, but I
like to remind myself that I will be the person reading this code in 6
months or a year, and I will be grateful for all the clarity I can
get. That said, maybe my use case differs from yours: I like to write
functions that make plots, so my data analysis programs are more
structured (eg standardize axes, titles, colors, plot types, etc).

Another, more functional-style API could be what ggplot does in R, using
the + operator to create a new plot with the extras.

Best,

Tamas

Tom Breloff

unread,
Apr 8, 2016, 9:07:09 AM4/8/16
to julia-users
On Fri, Apr 8, 2016 at 3:44 AM, Daniel Carrera <dcar...@gmail.com> wrote:
Hello,

I was looking through the API for Plots.jl


If you look just above that, note that I put a big warning that this section of the docs need updating.  Personally, I hardly ever use those mutating methods; but some people prefer that style, so I make it available.
 
Maybe I'm the only one, but I think all those exclamation marks are a bit extraneous and feel like syntactic noise.

It modifies a plot, and so follows Julia convention.  Anything else is likely to induce confusion.
 
I have been following Plots.jl because I'm interested in plotting. My use of Julia comes down to either making plots, or post-processing data so I can make a plots. I get the idea from Plots.jl that functions that end in an exclamation mark are supposed to modify an existing plot. So you get things like:

plot!(...)  # Add another plot to an existing one.
title!(...)
xaxis!("mylabel", :log, :flip)
xlims!(...)
xticks!(...)

If you don't want to plot like this, then don't!  There's a million ways to produce the same plot.  If you want to put all your commands in one line, this will work: plot(rand(10), title="TITLE", xaxis = ("mylabel", :log, :flip, (0, 20), linspace(1, 10, 20)))

Inline image 1
Plots figures out that log is the scale, (0,20) is the axis limits, and linspace(1,10,20) are the tick marks, which reduces a ton of clutter (not to mention you don't need to remember a complicated API).  One of the key goals of Plots is that you can use whatever style suits you.  (and feel free to open issues if there's something that you think can be more intuitive)
 
 In PyPlot, all commands edit the current plot unless you explicitly call `figure()` to create a new plot. You can also use clf() to clear the current plot. I think this is something that PyPlot / Matplotlib get right.

We can agree to disagree on this point.  It's clunky and error-prone.

Quick tip: you can choose to reuse PyPlot windows by default if you want:

# these will effectively call clf() before each command
using Plots 
pyplot(reuse = true)
plot(rand(10))
plot(rand(10))
plot(rand(10))


- Tom

Daniel Carrera

unread,
Apr 8, 2016, 10:44:24 AM4/8/16
to julia...@googlegroups.com
Hello,

On 8 April 2016 at 15:07, Tom Breloff <t...@breloff.com> wrote:

It modifies a plot, and so follows Julia convention.  Anything else is likely to induce confusion.

I do see your point of view, and of course it's your library. I also don't want to diss your work. I think the convention is about modifying inputs, rather than modifying "something". Functions that modify files don't get exclamation marks. On the other hand, Gtk.jl is a blend, with functions like push!() and delete!() but also destroy(), and signal_connect(). The authors just picked what made sense to them.



 In PyPlot, all commands edit the current plot unless you explicitly call `figure()` to create a new plot. You can also use clf() to clear the current plot. I think this is something that PyPlot / Matplotlib get right.

We can agree to disagree on this point.  It's clunky and error-prone.


Agree to disagree. This is just an opinion.

 
Quick tip: you can choose to reuse PyPlot windows by default if you want:

# these will effectively call clf() before each command
using Plots 
pyplot(reuse = true)
plot(rand(10))
plot(rand(10))
plot(rand(10))

Thanks.

Cheers,
Daniel.

Daniel Carrera

unread,
Apr 8, 2016, 12:55:29 PM4/8/16
to julia...@googlegroups.com
Hello,

Here is an example use case for me. I have several directories with data. I want to step through each directory, do some work, and plot some result; all in the same figure:

for dir in directories
    ... do some work ...
    plot(result)
end


Clearly it would be inconvenient if every time I have to do this (which is all the time) I had to make sure that the first set of results runs ``plot()`` and the others run ``plot!()``. How would you deal with this using Plots.jl? The solution I can come up with is:

plot()  # Plot nothing.
for dir in directories
    ... do some work ...
    plot!(result)
end

I guess that would be the best option.

Cheers,
Daniel.



On 8 April 2016 at 15:07, Tom Breloff <t...@breloff.com> wrote:

Steven G. Johnson

unread,
Apr 8, 2016, 5:44:14 PM4/8/16
to julia-users

On Friday, April 8, 2016 at 4:00:47 AM UTC-4, Tamas Papp wrote:
IMO an extra ! is a small price to pay for consistency --- you are after
all modifying an existing object. Avoiding globals is also a good
strategy.

Actually, it doesn't seem entirely consistent with Julia conventions. 

The standard Julia convention (borrowed from Lisp/Scheme) is that a ! suffix means that a function modifies *one of its arguments*.

Here, however, one of the arguments is not being modified, but rather some global object (a plot) is being changed.  Modifying global state is a different sort of side effect from modifying an argument.   And, in Julia, side-effects in general don't lead to a ! suffix.   (e.g. any I/O is a side effect, but functions like println and write in the Julia standard library don't have ! suffixes.)

Steven

Tom Breloff

unread,
Apr 8, 2016, 6:09:31 PM4/8/16
to julia-users


Actually, it doesn't seem entirely consistent with Julia conventions. 

The standard Julia convention (borrowed from Lisp/Scheme) is that a ! suffix means that a function modifies *one of its arguments*.

The `plot!` command  is primarily `plot!(plt::AbstractPlot, args...; kw...)`.  In this case it holds to convention.

I have a convenience `current()` which stores the most recently updated AbstractPlot object in a global, so that any plotting command without a leading AbstractPlot object gets it added implicitly.  (i.e. a call to `plot!(...)` is really a call to `plot!(current(), ...)`).

I think this strategy is better than alternatives, and isn't too far from accepted conventions.

Daniel Carrera

unread,
Apr 8, 2016, 7:19:00 PM4/8/16
to julia...@googlegroups.com
On 9 April 2016 at 00:09, Tom Breloff <t...@breloff.com> wrote:
The `plot!` command  is primarily `plot!(plt::AbstractPlot, args...; kw...)`.  In this case it holds to convention.

I have a convenience `current()` which stores the most recently updated AbstractPlot object in a global, so that any plotting command without a leading AbstractPlot object gets it added implicitly.  (i.e. a call to `plot!(...)` is really a call to `plot!(current(), ...)`).

I think this strategy is better than alternatives, and isn't too far from accepted conventions.


I understand that to you this seems intuitive, but to me it is completely counter-intuitive. The function that I'm calling is not changing any of its inputs. Telling me that behind the scenes it calls "plots!" is describing an implementation detail that can change and should have no bearing on the exposed API.

Another pertinent example is the write function:


write(stream, foo)


Using your line of reasoning you'd say that "write()" is modifying "stream" and therefore should have an exclamation mark. But this is clearly not how the Julia convention works. The fact that "stream" points to something that was modified is not enough to merit the exclamation mark.

I would argue that "plots(plt, ...)" is like "write(stream, ...)"

 
Cheers,
Daniel.

Kristoffer Carlsson

unread,
Apr 8, 2016, 7:52:46 PM4/8/16
to julia-users
I like the ! in Plots.

ben

unread,
Apr 8, 2016, 8:20:20 PM4/8/16
to julia-users
I also like the ! in Plots a lot.

Ben!

Tom Breloff

unread,
Apr 8, 2016, 8:30:48 PM4/8/16
to julia-users
I understand that to you this seems intuitive, but to me it is completely counter-intuitive. The function that I'm calling is not changing any of its inputs. Telling me that behind the scenes it calls "plots!" is describing an implementation detail that can change and should have no bearing on the exposed API.

It's not behind the scenes or an implementation detail. The "proper, exposed" API looks like:

plt = plot(rand(10))
plot
!(plt, xlim = (0,20))

Here plt is a julia object.  It's constructed during the first call, and changed in the second call.  This is the only form that should be used for "serious" work, as you shouldn't depend on global state in general.  For your original example, you were on the right track:

plt = plot(title = "...", xaxis = (...), ...)

for dir in directories
   
... do some work ...

    plot
!(plt, result, ...)
end

You can set up the plot attributes in the first call, and then add series (data) with your results.

In your original code, what if there's something in that "do some work" that calls a plotting command?  (answer... your code breaks)  So the mutating plot call without an AbstractPlot as the first argument is really only meant for quick, one-off, analytic work.  It's not meant to be the primary usage pattern.

I would argue that "plots(plt, ...)" is like "write(stream, ...)"

In this example, plt is a julia object that is mutated, whereas stream is effectively unchanged (it still points to the same file descriptor, or whatever it wraps).  

The better analogies (IMO) are:

plt = plot()
x = rand(10)

# these are similar
push!(x, rand())
plot!(plt, x)

# these are similar
show(io, x)
display(plt)


Daniel Carrera

unread,
Apr 8, 2016, 8:38:01 PM4/8/16
to julia...@googlegroups.com
Looks like I'm out-numbered.

Daniel Carrera

unread,
Apr 8, 2016, 8:52:34 PM4/8/16
to julia...@googlegroups.com
Ok. Thanks for the explanation.

Cheers,
Daniel.

Christof Stocker

unread,
Apr 9, 2016, 2:09:23 AM4/9/16
to julia...@googlegroups.com
I also prefer the ! so I know it modifies an existing plot. If I don't use a ! then I expect to create a new one

Patrick Kofod Mogensen

unread,
Apr 9, 2016, 3:22:37 AM4/9/16
to julia-users
+1 for !
Reply all
Reply to author
Forward
0 new messages