Re: Questions about API Design, composability and multiple arguments

Hi Nathan.

I can give you some off-the-cuff personal opinions on the questions you

> interface Store {
>     readonly attribute unsigned long length;
>     T              get (in unsigned long index);
>     void           add (in T t);
>     void           merge(in Store store);
>     sequence<T>    toArray();
>     Store          filter (in TFilter filter);
>     void           forEach (in TCallback callback);
> };
> (1) Adding multiple items
> What's the best approach to allow adding multiple items?
> - add(in T[] t)
> would this allow a single T not in an array to be passed in?
> can one define a function with WebIDL so that it accepts either a
> single item of type T or an array of T?

It wouldn’t allow this.  But you could overload to allow this:

  void add(in T t);
  void add(in sequence<T> ts);

> - add(in T... t)
> is the variadic approach better?
> also if at least one argument is required would the IDL have to be:
>   add(in T t, in T... ts) ?

The reason I don’t prefer variadic functions like this is that it makes
it more complicated to call if you already have an array of items built
up that you want to pass in.

  var ts = getItemsToAdd();
  store.add.apply(store, ts);

On the other hand, many built-in Array methods use the variadic

> - addAll(??)
> Would it be preferable to introduce an addAll function?
> If so, then the above two approaches also apply here, which one?

If the question is whether to use a different name rather than overload
‘add’, I don’t have much of an opinion.

> Also you'll note the toArray() method, if the advice above is to the
> above is to take the variadic approach, then how should one address
> importing an array - and further would the appropriate type for such
> an argument be sequence<T> or T[]? `addArray (in ?? t)`

If it’s just used an operation argument, then it won’t matter either
way.  If an Array is passed to an operation expecting a sequence<T>,
then a reference to that Array won’t be kept by the object (nor will it
modify the Array).  The array type T[] is best used if you want to have
an array that both user JS code and the DOM objects can keep a hold of
and modify.

> (2) Chaining by return
> Should the method add(in T t) return Store, the current store to
> which the T has just been added so as to allow chaining as such:
>    store.add(t1).add(t2).add(t3)

I’m not a big fan of that kind pattern.  Some are, though.  It obviously
breaks as soon as you want to return a meaningful value from the method.

> Likewise the same question can be asked about merge()? - however
> this adds in a twist because filter() will not modify the contents
> of the store and return a new Store (it acts like Store is
> immutable), whereas merge would modify the contents of the store and
> then return itself.
> Likewise again with forEach?

Right, you would have to judge when it makes sense to return the Store
and when not to.  User of the API would then have to be aware of when
they can chain method calls and when they can’t.

> (3) merge()
> Where merge(in Store store) is a method which adds non duplicated
> members of the store passed in to the current store, would this be
> better named 'import' or something else?

No opinion.

> Would it be wise to add a counterpart method which treated the store
> as immutable returning a new store (as filter does)?


> (4) counterpart methods
> Finally, just a quick one, is it worth considering adding
> counterpart methods for composability, as in given we have
> Store.add(T t) would it also be wise to include a method
> T.addTo(Store store) - and if so should it return the instance of T
> or the Store with t added?

Those kinds of methods *could* be handy for higher order functions, I
suppose.  Although in this specific case, it seems like you’d be more
likely to have an array of T objects rather than an array of Stores.

Cameron McCormack ≝

Received on Tuesday, 2 November 2010 01:10:57 UTC