Developer Blog

Casual APIs

This blog post is inspired by some ideas from Avdi Grimm’s excellent book Confident Ruby. Definitely read it if this topic interests you. It contains a ton more best practices.

Modern software projects typically combine different code bases like frameworks, libraries, or toolkits. The problem is that these independent code bases usually comes with their own subjective domain models, each one specialized for the task the respective library is supposed to perform. Hence, combining different code bases forces developers to deal with several overlapping domain models within their applications.

If the individual libraries are too strict and narrow-minded about the types of parameters they accept as input, combining them into a cohesive code base becomes cumbersome. The code starts to feel like a bureaucracy where despite you having the right data, it is not accepted because it is not in a particular format, and just out of principle there cannot be any exceptions. Adding the necessary explicit type conversations to method calls makes them more cryptic than necessary. Extrapolating the converting of types into explicit adapter layers adds bloat to the architecture.

Wouldn’t it be nicer if libraries were a bit more casual, flexible, and “forgiving”? That with a pragmatic “can-do” attitude they accept parameters in any usable format, and do the right thing in any situation, like a friendly and helpful human operator would do?

This blog post describes a convention to accomplish this through transparently massaging input data dynamically into the needed formats. The examples are in Ruby, but the described techniques apply to most dynamically typed environments and even some reasonably sophisticated statically typed ones.

Implicit and Explicit Conversions

Let’s say we write a text processor application. As part of that we have a method that moves the cursor on the screen. It needs the number of characters by which to move forward, and given the discrete nature of characters this should be an integer.

But we want this method to be casual. If our data happens to be another type of number, i.e. a floating-point, decimal, or even our own custom classes that represent numbers, we still want to be able to use it here. Some examples:

1
2
3
4
5
6
7
8
9
10
11
move_cursor 10                              # Integer parameter

move_cursor column_width * 1.1              # Float parameter while drawing
                                            # a table

move_cursor account.balance * column_width  # BigDecimal parameter while
                                            # drawing a chart

move_cursor configuration.border_width      # We know its the right number,
                                            # but aren't sure which exact data
                                            # type configuration values have

Ruby provides a number of conventions that allow us to do this elegantly. The first one is converter methods. Let’s use one in our method to make it accept all types of numbers and convert them to the type it needs:

1
2
3
4
def move_cursor pixels
  pixels = pixels.to_int
  # pixels is now an integer, and we can move the cursor based on it
end

By calling to_int on the argument, our method now works not only with integers, but with any number.

Notice that we use to_int here, not to_i. The difference between the two is that the former is an implicit conversion, while the latter is an explicit conversion.

Implicit converter methods convert closely related types into each other. This means types which can easily stand in for each other. If you need an integer, receiving a float is probably okay. You can (and should) expect that any Ruby code base calls implicit converter methods for you, hence their name.

Explicit converter methods convert unrelated classes, i.e. classes that shoudn’t be converted without the developer knowing about it. As an example, DateTime does not implicitly convert into an integer, because points in time are not really numbers, and shouldn’t pretend to be. It should still be possible to convert DateTime into a reasonable number if necessary, but you have to do that explicitly. Don’t expect that other code does such far-fetched things for you implicitly!

The cool thing about these converter methods is that they allow for architectures that are open for extension. This means you can make your own objects behave like numbers everywhere, even in other libraries, simply by adding a to_int method to them. And bam! you can use instances of your classes anywhere numbers are required! No more manual converting!

Below is a list of Ruby’s built-in converter methods. Use them as much as you can, and don’t reinvent them!

target class implicit method explicit method
Array to_ary to_a
Complex to_c
Enumerator to_enum
Hash to_hash to_h
Integer to_int to_i
IO to_io, to_open
String to_str when needing strings
to_path when needing filenames
to_s
Proc to_proc
Rational to_r
Symbol to_sym

Dynamic Type Casting

One side effect of these converter methods is that your classes have to be aware that they can be used as a number, and have to consciously implement the converter methods. That’s usually a good thing. But what if that’s not possible, like when you don’t have control over who calls your method? Or you use a library that doesn’t implement this protocol, and you don’t want to monkey-patch all of its internal classes?

Ruby provides another set of powerful converter methods for this case. These methods have the same name as the class they convert into. They can be seen as the bigger brothers of type-casting in statically typed systems. Bigger because these methods not only cast types, but also convert their parameter into the target class.

They are also siblings of constructor methods (funny how everything seems related here), with the difference that constructors always create a new instance based on the given value, while converters don’t create a new instance if that’s not necessary, i.e. when given a usable instance already. This makes them more efficient. Let’s play with Ruby’s built-in Integer method a bit:

1
2
3
4
5
6
Integer 100         # = 100  (ints get passed through)
Integer 100.1       # = 100  (floats are converted to int)
Integer "100"       # = 100  (parses strings)
Integer "0b100"     # = 4    (parses binary-encoded strings)
Integer Time.now    # = 133422 (calls to_i and to_int on the object)
Integer({})         # TypeError: can't convert Hash into Integer

As you can see, this method uses every trick in the book to convert its argument into an integer, and if that doesn’t work, it detonates an exception. Very convenient! Ruby provides such converter methods for many of the built-in classes: Array, Float, String, Integer, URI, or Pathname.

So if we have a print method that needs to convert its arguments into a string no matter what is given to it (everything that is given to it should be printed), we would best write it like this:

1
2
3
4
def print value
  value = String value   # Convert the parameter to a String, or fail here
  # print the string value now
end

Custom Converters

Let’s build such a converter method ourselves! Let’s say our text processor application should be modern and support colors. So we create our own Color class. Anytime a color is needed, developers should be able to provide one comfortably with whatever data about colors they have at that time. Lets take the method to set the current color as an example. We should be able to call it with parameters like these:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# HTML color strings
set_color '#102030'

# integer RGB values
set_color 10, 20, 30

# floating point RGB values
set_color 10.1, 20.1, 30.1

# existing Color instances
set_color red

# an array of RGB values
set_color [10, 20, 30]

# any object that has a 'to_color' method
set_color parent_container

That’s what I call a nice to use method that works with me! Here is how easily the method performs these conversions:

1
2
3
4
def set_color *args
  color = Color *args
  # do something with the 'color' variable here
end

As is visible, the method first massages its parameter(s) into the format it needs, in this case a Color instance, and then does its job with that value. Here is its implementation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Converts the argument(s) into a Color instance
def Color *args
  case args.first

    when Color
      args.first

    when Integer
      Color.new(*args)

    when Float
      Color.new(*args.map(&:to_i))

    # Parse HTML color code string of the form '#112233'
    when String
      Color.new(*args.first.scan(/^#(\w{2})(\w{2})(\w{2})$/)[0].map{|x| x.to_i 16 })

    # Call to_color method if the parameter has one
    when ->(arg) { arg.respond_to? :to_color }
      args.first.to_color

    # Convert other color classes to our class by using their RGB values
    when ->(arg) { arg.respond_to? :to_rgb }
      Color.new args.first.to_rgb

    # Fail if there is no known type match
    else
      raise TypeError, "Cannot convert #{args.inspect} into a Color"
  end
end

Simple, straight, easy to understand Ruby, with clear expression of intent.

Best Practices

  • Make your APIs casual, and thereby easy and intuitive to use. If there are different legitimate types of parameters, your API should do the work to support them, not your users. This makes your API robust, meaning your users can throw all sorts of different input at them, and it does the right thing.

  • When unsure about the parameters given to your methods, for example when they are called by other people’s code, convert the parameters into the expected type or fail.

  • Don’t give up too early. Try all suitable type conversions before you reject a parameter.

  • You can take it easy on data types for parameters, but you should fail reliably for bad arguments. For example, if you allow strings to be provided for colors, and the string does not contain a color code, your code should fail, because that’s most likely a bug. If you get an id for a product, and the referenced product does not exist, there should be an immediate failure as well.

  • Use integration tests (aka feature specs) to make sure everything is plumbed together correctly, and the application functions as it should.

  • To keep things efficient, only convert types at the public boundaries of your code, not in each internal method. Similar to how the visa of tourists is only checked when they enter a foreign country at its border, and not on each bus ride inside the city.

  • Document non-implicit conversions so that other developers know about them.

Conclusion

Done correctly, casual APIs are both robust and intuitive to use. Like a helpful human operator, they do the right thing in all situations no matter what input you throw at them. This allows developers to focus on calling libraries with the right data instead of the right data format.

There are several ways to implement this pattern, depending on how strict you want to be about your input. Done consistently, casual APIs allow for casual collaboration of independent libraries in modern, open-source based code bases.

Happy dynamic type massaging! :)

Comments