Re: Maybe we should think about Interface.isInterface functions again

On Jul 31, 2013, at 12:02 AM, Jonas Sicking wrote:

> On Tue, Jul 30, 2013 at 4:38 PM, Allen Wirfs-Brock
> <allen@wirfs-brock.com> wrote:
>> 
>> On Jul 20, 2013, at 5:25 AM, Boris Zbarsky wrote:
>> 
>>> On 7/20/13 2:14 AM, Domenic Denicola wrote:
>>>> Is it really worthwhile adding this surface area to all the APIs just to avoid the edge cases like HTMLElement.prototype instanceof Node? In what situation do we envision that kind of "false positive" being a problem?
>>> 
>>> https://twitter.com/kuvos/status/348019487120957440 and following discussion has a description of such a situation arising in real-life code: the code wants to test for ownerDocument being non-null, but it only wants to do this on Node objects, of course.  And HTMLElement.prototype is one of the objects it ends up being passed, for whatever reason...
>>> 
>>> -Boris
>> 
>> It would be really useful to see some more complete examples of where this is sort of dynamic discrimination is actually needed in application code. This style of type testing has a smell to it and it isn't clear to me whether the use cases you see arise from poorly designed APIs or whether you think there are situations where it makes sense in a good design.
>> 
>> Jonas, mentioned the filesystem API but it isn't clear to me why an instance side isDirectory method wouldn't suffice for what I imagine is this use case.
> 
> I take it you are basically asking why there is a need to have
> 'instanceof', or anything like it. Is that correct?

No, not really my point here.  However, in the world of dynamic object languages, use of  instanceof-like tests have been frowned upon for decades.  For example see: http://stephane.ducasse.free.fr/FreeBooks/WithStyle/SmalltalkWithStyle.pdf pages 97-98: "Guideline 122: Avoid #class, #isKindOf:, and #isMemberOf: to check for class membership" 

I was more thinking about situations like the overloading formal parameters or or function results.  For example, in:
    function foo(x) {
       //x may be either a Bar object or a collection of Bar objects
    }

This is sometime we avoid in new ES built-in APIs.  For example, in ES 6 we will have:
      Array.of(x)  //create an array containing the single element x
      Array.from(x) //create an array containing all the elements of the collection x

In other words, rather than overloading a single method, which might be good style in a statically typed-language, we prefer to use different method names in such situations and require programs to more explicitly express their intent via choice of method name..

> 
> It's not entirely clear to me how you mean that the isDirectory
> function would behave, or where it would live. Do you mean that we
> would add a isDirectory() function on all Directory instances which
> always return true?
> 
> That would mean that you write code like:
> 
> root.get("somename").then(function(result) {
>  if ("isDirectory" in result) {
>    // No need to actually call .isDirectory since it always returns true.
>    ... it's a directory ...
>  }
>  else {
>    ... it's a file ...
>  }
> });
> 
> This approach definitely works. Basically any time that you can have
> objects of different types and want to find out what type of object
> you have, you have to find some property that is unique for each type
> that you want to check for.
> 
> The downside of this approach is that it's pretty fragile though. Any
> time a property is added to existing classes, it risks creating a
> situation where you are breaking type testing.

something like that, but I would do it something like:

//first approach:

I assume that you have File and Directory objects and that, in some sense, Directories are also Files.  In other words, Directory might be implemented as a subclass of File. 

I would define the property "isDirectory" on both File.prototype and Directory.prototype For files its value is false and for Directories its value is true.

I would test it as:
  if (result.isDirectory) {... //do directory specific processing
  ...

Note that if result was neither a Directory or File and does not have such a property, result.isDirectory is a falsy value so the directory path of the logic will not be taken.  Strictly speaking,  File.prototype does not need to have a 'isDirectory' property but it is probably a better design to be explicit about such things.

This is a reasonable approach, but lets consider how it might be used and how we might want to extend it in the future.

//second approach 
Presumably, the need to discriminate between a File and a Directory is most likely to arise when trying to traverse directory structures.

I assume that Directory probably has a method (let's call it 'itemsDo' for iterating over the files and subdirectories contained in a Directory.  Then we might write a traversal such as this using the above API:

rootDir.itemsDo(function visit(fileOrDir)  {
   console.log(fileOrDir.name);
   If (fileOrDir.isDirectory) fileOrDir.itemsDo(visit)
});

Now what happens, if we want to extend the file system to recognize zip files and for traversal purposes we want to treat them as sub-item containers similar to Directories. Should we make 'isDirectory' true for zip files? We might to make the above traversal work.  But what if there are other directory behaviors that zip files don't support?

A different API design might make this extension easier to do.  Note that this traversal really doesn't need to know whether an item is a File or Directory.  It only needs to know if an item has subitems that are retrievable using itemsDo. If itemsDo was defined for both File and Directory we could have coded the traversal as:

rootDir.itemsDo(function visitot(dirItem)  {
   console.log(dirItem.name);
   dirItem.itemsDo(visitor)
});

assuming that:
File.prototype.itemsDo = function (visitor) {return};  //files don't have subitems.

Note that this enables the traversal visitor to be written without an explicit test WRT whether an item is a Directory or not.

It will work even if we don't include 'isDirectory' in our API design.

Using this design, if we want to add zip files we don't have to classify them as a kind of Directory.  All we need to do is define ZipFile.prototype.itemsDo such that it returns appropriate items for a zip files contents.

This polymorphically dispatched  behavioral  style of API design has proven very successful for dynamic object oriented languages.  Rather than trying to identify specific implementation "classes" for various objects, this approach focuses is on identifying the common behaviors that need to be support by objects from different classes and implementing those behaviors in a manner that makes class identification irrelevant.

Allen

Received on Wednesday, 31 July 2013 18:50:45 UTC