GTC API (how to design a rich interface)

After written about the origin and goals of Graphics::Toolkit::Color -- let's take a look at the public methods and make it a little study of good API design. But lets work our way up from a few examples:

One method we got was named gradient_to. I thought it was a good idea because you had the caller (invocant - the GTC object) and the first argument (second GTC object or data that can be handled by the constructor) so:

$color1->gradient_to( $color2, 5)

felt like a natural connection of the two for demanding a gradient of 5 colors. But what is wrong with this picture?

Well for one, the little word to acts like an argument name but is part of the method name. This can can work for methods with only one argument or were the rest is obvious or only optional. Well technically the 5 is an optional argument but that would render it for most cases useless. But the real issue began when I had more ideas for arguments. A factor determining the dynamics of nonlinear gradients and sometimes you want to compute you gradient not in HSL but in another color space. So I changed it to:

$color1->gradient( to => $color2, steps => 5, dynamic => 4, in => 'RGB');

This is much better. The method name is just like a single verb, telling you the action to be done. And the named arguments are like the adjectives, modifying the action. Your free to order them or leave some out and all are clarified not just the special first one.

But with named arguments its also easier to compound much more (potential) functionality into one method without the giving it a overburdened feel. For instance, if want to have a method for random colors, I would only need to add another named argument, that defines how much the color is allowed to deviate from its original place in the gradient. This way i don't need another method with an difficult name, you have more control how random the colors are and how their bubble of possible value is placed. If i leave the argument out the randomness defaults to zero. I only have to give the named argument a long and precise enough name so its sticks out and its recognizable that this method does a little more.

In same manner i reformed almost all getter into one super-getter. But before I will come to that there is another issue with the gradient argument for dynamics.

In previous versions there were values allowed from 0 .. inf even after 10 the differences became marginal. The mistake I made was for reserving values above 1 for gradient leaning in on direction and smaller than 1 into the other direction. This was a lazy solution just using the value as exponent in the actual formula. For the average user it was not intuitive to calculate the effects of smaller than one exponents in the head. Plus half of the value range was illegal. Why introduce random error traps. The new version accepts all numbers and has its neutral point at zero (linear gradient). Number greater than zero let the gradient lean into one direction and so forth.

Alright on to the super-getter. The constructor accepts a huge host of inputs of many color spaces and formats. I think its good and logical if we can also output all that - but only with the methods: name (for color names) and values (for numerical values). BTW even if you defined your color like '#0000FF', name still will return blue.

In most cases you want the RGB triplet as a list and that what you get if you call values without arguments. But a full fledged call would be:

$color->values( in => 'HWB', as => 'css_string', range => 'normal' );

Calling for a HWB (hue whiteness blackness) value triplet, but in a format like in a CSS definition: 'hwb( 240, 30, 60)'. The range argument allows you you renormalise the values. In this case you would get the three hwb values in the 0..1 range (2/3, 0.3, 0.6). This can be very handy for instance if you don't like that GTC handles CMY and CMYK values as normal. If you prefer values between 0 and 255 (like in RGB) you simply insert: format => 255 and you will get what you want. If you want to get anal you could request ranges from -100 .. 100 by choosing the syntax: range => [[-100, 100],[-100, 100],[-100, 100]]. Here it becomes clear that it would be madness to create an extra getter for every combination of color space, format and normalisation. But most usual use case are jsut the values in one color space, lets say HSV:

$color->values( 'HSV' );

With this method I reduced the amount of methods so we have now one constructor, 2 getter (as just mentioned), one method to measure all kind of distances (difference between two colors), 3 methods to compute a related color and 3 (soon 4) methods to compute color sets. This is very rich but still navigatable and allows for a structured documentation.

But distance method and the 3 for single color creation should be content of the next part.


PS. Yes these "named arguments" are really positionals which I just:

%args = @_; # checking for % 2 ofc

But this way you can omit the (optional) curly braces ($color->values({ in => 'HWB', as => 'css_string', range => 'normal' }); works too), which makes the syntax much nicer.

Leave a comment

About lichtkind

user-pic Kephra, Articles, Books, Perl, Programming