Articles

  • No categories

Implicit Coercions in Whiley

The issue of [[Type conversion|implicit coercions]] in Whiley is proving to be a particularly thorny issue.  The following motivates why (I think) coercions make sense:

real f(int x, real y):
    return x + y

real g(int x, int y):
    return f(x,y)

I believe the above should compile without error.  However, this requires an implicit coercion from int to real in several places.  Some statically typed programming languages (notably [[ML (programming language)|ML]]) simply don’t perform any implicit coercions.  Instead, they require explicit coercions in the form of type casts.  Under this model, the above code would be:

real f(int x, real y):
    return real(x) + y

real g(int x, int y):
    return f(x,real(y))

To me, this seems rather cumbersom and, when it’s clear from the context, I want the compiler to do this for me.

So, what coercions could we support in Whiley?  Here’s a taster:

  • int –> real
  • {int x, int y} –> {int y}
  • [int] –> {int}
  • {int->int} –> {int,int}
  • [string] –> {int->string}

Unfortunately, whilst this looks all good on the surface, there are some tricky cases.  Here’s one example:

define Rec1 as { int x, real y }
define Rec2 as { real x, int y }
define Rec12 as Rec1 | Rec2

int f(Rec12 r):
   if r is Rec1:
       return r.x
   else:
       return r.y

int test():
   z = {x: 1, y: 1} // z has type {int x, int y}
   return f(z)

The problem here is that we cannot determine whether to coerce z to Rec1 or Rec2.  I guess we should report an ambiguous coercion error. Which immediately raises the question of how, in the general case, I detect this.

9 comments to Implicit Coercions in Whiley

  • Mathnerd314

    I think the problem is the union in general, not implicit coercions in particular. For instance, suppose you had:

    define Rec1 as { int x, int y }
    define Rec2 as { int x, int y }
    ...
    z = {x: 1, y: 2}
    ...

    and the rest as in your post. What should happen? (IMO test should return 1)

  • Hey,

    In fact, what happens is that you get a syntax error. Given your definitions then the compiler correctly reasons that Rec1|Rec2 == Rec1. Therefore, it reasons that the statement return r.y is dead-code, and reports a syntax error!

    D

  • Mathnerd314

    Okay, I guess that works. Anyways, in your original example, the behavior should be similar: the code tries coercing {int x, int y} to Rec1, succeeds, and therefore takes the return r.x branch.

  • Mathnerd314

    Also, I just realized you have a typo in the post: shouldn’t return g(x,y) be return f(x,y)?

  • shouldn’t return g(x,y) be return f(x,y)?

    Oh yeah … doh!!

    Fixed

    (thanks)

  • the code tries coercing {int x, int y} to Rec1, succeeds, and therefore takes the return r.x branch.

    Right, so it could do this. But, it could equally choose Rec2 … and generally compilers report errors on this kind of ambiguity.

    Also, using something like order of declaration in the source file is not really a solution for me, since I throw away all that information (and keeping it would be really awkward).

  • Mathnerd314

    Ah, I think I see our point of disagreement. I think of types as contracts aiding optimization of a loosely and dynamically-typed language, whereas you expect types to mimic the behavior of static types fairly closely. So when I see the type signature “int f(Rec12 r)”, I think of it as a function taking some object that can eventually be converted into a Rec12 and returning some object eventually convertible to an int, whereas you think of it as taking an actual Rec12 and returning an actual int.

    So in my view, {x: 1, y: 1} is clearly an instance of Rec12, so the contract is upheld; then all the types are erased, and the if-else establishes a clear preference of Rec1 over Rec2.

    From your view, it would involve returning a closure that only attempted the “real” coercion from int to real once it knew whether a value of Rec1 or Rec2 was desired from the union.

    In Javascript-ish pseudocode: (I hope the blog doesn’t eat it)

    union = [{int x, real y}, {real x, int y}, ...]

    function coerce(obj, union) {
    coercable = false
    for(type in union)
    if(coerce(obj,type))
    coercable = true
    if(!coercable)
    error "invalid union coercion"
    else
    return function(realtype) {
    return coerce(obj,realtype) || error "wrong type of union"
    }
    }

  • From your view, it would involve returning a closure that only attempted the “real” coercion from int to real once it knew whether a value of Rec1 or Rec2 was desired from the union.

    Yes, exactly! But, that’s not the route I want to go down. Whiley is strictly a statically typed language, so I need to disambiguate this case somehow …

  • […] something of a headache as, in the context of structural subtyping, they’re quite tricky.  This post provides some insight into the problem, as does this […]

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>