Polymorphic functions

home

 

Most languages have a notion of a polymorphic function. Versions from each can look and feel very different, but it's pretty much the same idea in them all. I'll stick with one that finds the price of any 2 items. In Python:

// requires a1 and a2 to have dot-isValid and dot-price
def priceSum(a1, a2):
  p : int = 0
  if a1.isValid(): p += a1.price()
  
  if a2.isValid(): p += a2.price()
  return p

This is almost cheating, since Python types are completely dynamic. All Python functions are polymorphic, whether we want them to be or not. The interesting thing is how we need, in a practical sense, to let users know about the isValid() and price() requirements.

The C++ version is about the same, except we need to tell the system that we can have a variable type using <T>:

// requires type T to have dot-isValid and dot-price
double priceSum<T>(T a1, T a2) {
  double p=0;
  if(a1.isValid()) p+=a1.price();
  if(a2.isValid()) p+=a2.price();
  return p;
}

This does one new thing -- it requires that the inputs be the same type.

We don't love informally communicating the need for price() and isValid(). Ideally, we'd have a formal way. Java does that with an abstract interface listing the requirements:

interface priceable {
  bool isValid();
  double price();
}

double priceSum(priceable a1, priceable a2) {
  double p=0;
  if(a1.isValid()) p+=a1.price();
  if(a2.isValid()) p+=a2.price();
  return p;  
}

That's about the shortest way to communicate the requirements. We might even re-use priceable. So far this is simply better. Except the example isn't complete. Classes now need to register as priceables:

class Toy implements priceable { // <-we needed to add "implements priceable"
  public bool isValid() { ... } // Even though we have the...
  public double price() { ... } // ... necessary functions.
  ...
}

If you think of the classes as pre-existing, which just happen to have common functions, this is unreasonable -- we shouldn't need to go back and add implements priceable to them all. But realistically, we planned priceable ahead of time. We thought about it when we made (or re-did) each class. Advertising that is merely good commenting.

But the interfaces introduce more complexity. We'll often have 1-function interfaces, for example IComparable for a single Compare function. Or a 4-function interface over-specifying the requirements.

Scala allows us to create a function-use-only interface (they call it a structural type). Classes don't and can't tells us whether they implement type priceable:

type priceable = { def isValid():Boolean, def price():Double }

// can be called using any type which happens to have isValid() and price():
def priceSum(a1 : priceable, a2 : priceable) = {
  var p : Double = 0
  if(a1.isValid()) p+=a1.price()
  if(a2.isValid()) p+=a2.price()
  p
}

All-in-all, these all seem about the same to me. You write a polymorphic function which, one way or another, has an interface for the inputs. You can make it formal, but the compiler will catch it either way (unless you're in Python).

Or flip it around. Suppose a class formally implements priceable -- what does that get us? Technically it gets us all the fun of inheritance. But if priceable was created to enable a polymorphic function, we'll never want it for anything more than that. We'll never want a list of priceables.

 

Now for a boring detail. C# appears to have polymorphic template functions, but it doesn't. double priceSum<T>(T a1, T a2) appears to allow any type. Except C# subscribes to the philosophy that you need to specify what the type can do. Alone T means the minimal type, which can't do anything. To make it useful, you need something like where T priceable. In other words, C# uses the formal interface method. Using this funny where method instead of simply the type, allows them to declare and return T's of the real subtype.

 

 

Comments. or email adminATtaxesforcatses.com