1

There are a couple places in the Closure library where interfaces have an addImplementation/isImplementedBy pair of functions to do runtime type checking on the interface (similar to this answer). I'm not entirely a fan of this solution where I have something very simple. Is there any way to do duck typing with ADVANCED_OPTIMIZATIONS enabled? Say I have an interface, and a component that takes special action on children with that interface, e.g.:

/** @interface */
MyInterface = function() {};

MyInterface.prototype.doSomething = function() {};

/**
 * @constructor
 * @extends {goog.ui.Component}
 */
MyComponent = function() {
   ...
};

/** @inheritDoc */
MyComponent.prototype.addChild = function(child, opt_render) {
  goog.base(this, 'addChild', child, opt_render);
  if (child.doSomething) {
    child.doSomething();  
  }
};

Will ADVANCED_OPTIMIZATIONS consistently rename that "doSomething" property with the implementations? If not, will adding a type union ensure that it will? e.g.

/**
 * @param {goog.ui.Component|MyInterface} child
 */
MyComponent.prototype.addChild = function( child, opt_render) {
  if (child.doSomething) {
    child.doSomething();  
  }
};
Community
  • 1
  • 1
flatline
  • 42,083
  • 4
  • 31
  • 38

2 Answers2

2

This is what @record was added for. You'll need to be using a pretty recent version of the compiler to make use of it (something dated at least 2016).

Just replace @interface with @record and you should get the behavior you desire. The compiler will rename things consistently.

Chad Killingsworth
  • 14,360
  • 2
  • 34
  • 57
  • Thanks, I did not know about `@record`, it looks very useful, generally. In the case of the type narrowing I'm doing here, I'm not sure if it adds anything - I still want to check for an implicit interface. I realized after the initial post that I can just typecast the variable to the interface and then check for the property - will this only work with `@record` or will it still work with `@interface`? – flatline Mar 22 '16 at 13:26
  • 2
    An object won't match an interface without an explicit `@implements` annotation on it. It will match a record simply by having a matching signature. You will still need your union type to tell the `addChild` method that the interface can be passed in. – Chad Killingsworth Mar 23 '16 at 20:18
1

I left Chad's answer as the resolution, but revisited this once I got compilation working and, for the sake of posterity, am posting a more detailed answer for the general case of duck typing.

You can perform dynamic downcasting with ADVANCED optimizations on as follows:

var maybeRecordType = /** @type {MyRecordType} */ (someObject);
if ( maybeRecordType.propertyInQuestion ) {
  maybeRecordType.propertyInQuestion();
}

You lose compile-time checks this way obviously if there are collisions on the property name between two possible types being passed, but that is always a risk with duck typing.

flatline
  • 42,083
  • 4
  • 31
  • 38