----- Original Message -----
From: "Tim Hunter" <cyclists / nc.rr.com>
Newsgroups: comp.lang.ruby
To: "ruby-talk ML" <ruby-talk / ruby-lang.org>
Sent: Tuesday, August 20, 2002 5:31 PM
Subject: What makes a "good" Ruby extension?


> So I'm reading the "Comparing Gui Toolkits" wiki page
> (http://www.rubygarden.org/ruby?ComparingGuiToolkits) and I notice that
> the author characterizes one of the Ruby interfaces as "Perl-like" and
> another as "immature." As someone who's been busy beavering away at his
> own Ruby interface to an existing C library for some months now, I'm
wondering,
> just what makes a "good" Ruby interface? (After all, I don't want to
> embarrass myself by offering a bad one to the Ruby community :-)

Well, if it's on the Internet, it must be true. :)

> Going by my own standards for software, I'm assuming that a good Ruby
> interface should be
>
> 1) usable, that is, not buggy, reasonably efficient, and predictable
> 2) complete, that is, provides a complete interface to whatever it's
> interfacing with
> 3) documented, (no point having an interface that you can't figure out)
and
> 4) supported, so you have some hope of getting bugs fixed, etc.
>
> Does this sound right? What else is important? I'm especially intrigued
> by the notion of a "Perl-like" binding vs. a
> "Ruby-like" binding. What makes an interface "Ruby-like?"

An interesting question. I'll offer an opinion or two.

An API should take advantage of the things Ruby does
well -- the things that make it Ruby -- WHEN this is
appropriate.

Most of my examples here will deal with only the API
that is exposed to the user... if we got into coding
style, I could probably come up with even more.

Examples:

1. Use class variables and methods in the API as needed.
2. If you have a collection or list, consider giving it
an iterator. Consider more than one kind, e.g. with_index.
3. If you do an open/close or start/stop, consider using
an "non-iterating" iterator for this. Examples are
File.open, Mutex#synchronize, and Dir.chdir (the new one).
4. If a method takes an object, consider whether it might
be meaningful for it to take a block instead or in addition.
5. Make use of nil where appropriate.
6. Make use of the existing modules, especially Enumerable
and Comparable, where appropriate.
7. Use symbols if/when appropriate. Sometimes they make a
good substitute for string constants or arbitrary integer
values.
8. Create meaningful exception classes as needed.
9. Provide objects with to_a, to_s, to_i, etc., as needed.
10. Give objects good 'inspect' methods.
11. Remember that often it matters less what class a thing
is than what methods it responds to. One of my favorite
examples: Suppose your method appends each thing in a collection
onto an object supplied by the caller (obj << item). This
will work whether the 'obj' passed in is a string, array, or
file (since you can meaningfully append onto any of them
with a << operation).
12. As regards #9, we also have to_ary, to_str, and (I think?)
to_int.
13. If you define some kind of numeric class, make sure it can
be coerced as needed.
14. Subclass as needed. This is just good OO besides good Ruby.
Subclass your own classes and/or the predefined ones.
15. Use hashes when convenient/appropriate. They're powerful.
16. As regards #11 above, remember to hide implementation
details to de-couple with the caller. If a hash has numeric
keys, it resembles an array (if you only use [] and []=). Or
it could even be your own special data structure.
17. Use readers/writers/accessors when appropriate.
18. Don't be afraid to add to existing classes/modules when
it makes sense to do so. That's why they're open.
19. An occasional alias can help an API. (Some people don't
like this, but I do.) In the core, indexes and indices are the
same for Arrays. For Strings, size and length are the same. Etc.
20. When an object needs to act "almost" like another object,
minus some functionality, consider delegation (delegate.rb).
21. When appropriate, make use of the ? and ! suffixes in the
standard way.
22. When appropriate, use default values for parameters.
23. Use Struct when it will help you. This will often prevent
indexing into arrays with arbitrary indices.
24. Be platform independent when you can. There are things like
File::SEPARATOR (?) that help with this.
25. Consider whether it makes sense to marshal an object. If so,
try to write the class so that it remains marshallable.
26. Overload operators as needed. That's why we have the capability.
27. In your API, try to use terminology consistent with the rest
of the Ruby world. (Sigh. I don't always do this either.)
28. It's a good idea (though I don't practice what I preach) to
try to support non-US and/or non-ASCII users to the extent you can.
29. Use singleton methods for objects where it makes sense. For
example: In a GUI, you might allow the user to attach a method
directly to a Button object.
30. Worry about performance last, and only when needed. (Some may
flame me for this.) If you really need great performance, write
a C extension. Otherwise stay in Ruby. Conceivably you might even
offer options for some pieces.
31. Allow users to represent their data in creative ways. If your
"foo" method takes an array, see if it makes sense for it to take
a hash also (or any object that knows [] and []=). If it takes a
string, see if a regex makes sense. Et cetera...
32. Make use of method_missing where appropriate. At the very
least, catch undefined methods that may be generated/called at
runtime. Hmm, unneeded? Ruby does it for you...
33. If you add to a class or module, go to the highest meaningful
level of generality. Don't add to File if it belongs in IO. Don't
add to a dozen classes if it belongs in Enumerable.
34. Consider making objects thread-safe when appropriate, since
Ruby has thread capabilities.
35. Consider a "hybrid iterator" (if I may call it that) which
returns a list when called without a block.
36. Don't forget that Ruby is dynamic. I'm not sure what I mean
by saying this, but don't forget it.  :)
37. Avoid camelCase in your API. Some like it, some use it, but
it isn't done in the core, I think. Likewise avoid using capitalized
method names (unless perhaps they perform data conversion, like
Integer). Underscores are generally accepted (i.e., used in the core).
38. Don't force users to use global variables or the Perl-like
"swearing" variables ($!, $_, etc.).
39. Don't forget there is such a thing as a Range class. Allow
users to pass in ranges where it makes sense.
40. Allow arbitrary numbers of parameters where it makes sense.
41. Make error messages and exceptions as meaningful as possible.
You can include data in them when it helps.
42. Use Rubylike ways of dealing with data. For example, don't
represent a time as an array of numbers or a string, but as a
Time object. If you need to convert to/from the other representations,
provide a way to do so.

Oh, yeah. And wear sunscreen.

Hal Fulton