views:

121

answers:

1

I'm taking a course on programming languages and the answer to "when is a function a sub type of another function" is very counter-intuitive to me.

To clarify: suppose that we have the following type relation:

bool<int<real

Why is the function (real->bool) a subtype of (int->bool)? Shouldn't it be the other way around?

I would expect the criteria for sub typing functions to be: f1 is a subtype of f2 if f2 can take any argument that f1 can take, and f1 returns only values that f2 returns. There clearly are values that f1 can take, but f2 can't.

+4  A: 

Here's the rule for function sub-typing:

Argument types must be co-variant, return types must be contra-variant.

Co-variant == preserves the "A is a subtype of B" hierarchy for the type of the results parameter. Contra-variant == reverses ("goes against") the type hierarchy for the arguments parameter.

So, in your example:

f1:  int  -> bool
f2:  bool -> bool

We can safely conclude that f2 is a subtype of f1. Why? Because (1) looking at just the argument types for both functions, we see that the type hierarchy of "bool is a subtype of int" is in fact co-variant. It preserves the type hierarchy between ints and bools. (2) looking at just the results types for both functions, we see that contra-variance is upheld.

Put another way (the plain English way I think about this subject):

co-variant arguments: "my caller can pass in more than I require, but that's okay, because I'll use only what I need to use." contra-variant arguments: "I can return more than the caller requires, but that's okay, he/she will just use what they need, and will ignore the rest"

Let's look at another examples, using structs where everything is an integer:

f1:  {x,y,z} -> {x,y}
f2:  {x,y}   -> {x,y,z}

So here again, we're asserting that f2 is a subtype of f1 (which it is). Looking at the argument types for both functions (and using the < symbol to denote "is a subtype of"), then if f2 < f1, is {x,y,z} < {x,y} ? The answer is yes. {x,y,z} is co-variant with {x,y}. i.e. in defining the struct {x,y,z} we "inherited" from the {x,y} struct, but added a third member, z.

Looking at the return types for both functions, if f2 < f1, then is {x,y} > {x,y,z}? The answer again is yes. (See above logic).

Yet a third way to think about this, is to assume f2 < f1, then try various casting scenarios, and see if everything works. Example (psuedo-code):

   F1 = f1;
   F2 = f2;
   {a,b}   = F1({1,2,3});  // call F1 with a {x,y,z} struct of {1,2,3};  This works.
   {a,b,c} = F2({1,2});    // call F2 with a {x,y} struct of {1,2}.  This also works.

   // Now take F2, but treat it like an F1.  (Which we should be able to do, 
   // right?  Because F2 is a subtype of F1).  Now pass it in the argument type 
   // F1 expects.  Does our assignment still work?  It does.
   {a,b} = ((F1) F2)({1,2,3});
Aaron F.
Sorry, my explanation was longer than I had hoped.
Aaron F.
Longer but not verbose and worth reading :)
Larry Watanabe
I'm still a bit confused. If {x,y,z} is a subtype of {x,y}, why is bool a subtype of int? In the first case {x,y,z} is bigger than {x,y} yet in the other example bool is smaller than int.
Epsilon Vector
Because you can cast an int into a bool. (In most programming languages, anyway. e.g. treat zero as false, all other integer values as true).
Aaron F.
So basically subtyping is not about cardinality of its set of values? Meaning, it's not the same as a subset in the sense that a subtype's cardinality may be bigger than its superset's (as in the {x,y,z} vs {x,y} case) or smaller (as in the bool vs int case).
Epsilon Vector
Don't focus on cardinality; I think it'll only confuse you. Focus on type hierarchy between two given types. e.g. a bool can be said to be a subtype of an integer, because given an integer, I can reasonably cast it into a bool. The reverse is not true.
Aaron F.