Defective Language

Leaf: Simple type conversions

One of the problems with C++ is the way it handles type conversion. It inherits a lot of implicit conversion from C and then adds on additional conversions. The problem with all this implicit conversion is that it allows conversion to types which can’t properly represent the input type: one can end up losing data and sometimes calling the wrong function.

While working on Leaf I want to avoid this. Implicit conversion is required, and really convenient, but it shouldn’t be dangerous. The basic rule I’m working by is that an implicit conversion must result in a value which is 100% equivalent to the input value. As I introduce user types into Leaf the concrete details of this become important. Let’s take a look at a few cases.

Tuple Conversion

Given a user type:

type point = {
	var x : integer,
	var y : integer,

It makes sense to allow this to be initialized from a compatible tuple:

var a : point = { 1, 3 };

However I think it doesn’t make sense to allow conversion in the opposite direction:

var b : { :integer, :integer } = a;

This would erase type information and become less rather than more specific. At first it appears not to break the rule about losing information: the values 1 and 3 are perfectly represented in the tuple. It does however lose the “point” type, and I’ll show a bit further down why this is a problem.

I’m also thinking that tuples with named bits only convert if their names align.

var c : { x : integer, y : integer } = { 3, 4 };
var a : point = c; //okay
var b : { :integer, :integer } = c; //error (less specific again)

var d : { real : integer, imag : integer } = { 9, 8 };
a = d; //error
c = d; //error

As the names don’t match it makes sense to disallow this: a user wouldn’t expect these types to convert as they don’t represent the same thing. Although the conversion would capture the values exactly, there would be a loss of type information.

Unordered Names

There is however one special case I’m not sure about, what if the names do match but not in the same order?

var e : { y : integer, x : integer } = { 1, 3 };
a = e; //Okay or error?
c = e; //Okay or error?

At first I’ll disallow this since it is hard to support, but also because it can be added later with no ambiguity (it’s always unambiguous to give meaning to something which was previously disallowed — it’s how I’m structuring the language for future expansion).

Why Disallow

Now it becomes clear why conversion can only go in a more specific direction — where more specific would include gaining a concrete type and less specific would be reduction to a generic tuple. Otherwise you’d be able to construct conversion chains that don’t make sense. For example, given:

type point = { x : float, y : float };
type complex = { r : float, i : float };

You don’t expect these types to be able to convert as they are unrelated.

var a : point;
var b : complex;
a = b; //error

Yet if we allow conversion to less specific, even anonymous tuple types, the above basically becomes possible via a temporary anonymous tuple.

 var c : { : float, : float };
 c = b;
 a = c;

The general rule for conversions in Leaf is that any chain of implicit conversions could be considered valid. Therefore if the above example with temporary is allowed to work, the prior example without the temporary ought to work too. Hence the rule to disallow loss of typing. We have a language then which allows gaining type information in a single direction and prevents loops and nonsensical conversions.

2 replies »

  1. If you introduce support for unordered names with the current conversion logic, you will end up with having a conversion act differently if it is assigned to a temporary variable, which I think you would not like.

    var e : { y : integer, x : integer };
    var a : point;
    var c : {:integer, :integer} = {1,3};
    e = c;
    a = e;

    a now has the value {x:3, y:1}. Direct assignment from c would have given it the value {x:1,y:3}.

    One solution would be to not allow gaining of type information when converting neither (or you could allow losing type information but not gaining). When assigning you would have to do a = {x:1, y:3}, instead of a = {1,3}, which /is/ a bit tedious, but may be fine. (potentially, the compiler could detect when constant structure expressions are used as rhs and automatically add typing to it. So while the assignment e=c; above would give an error, e = {1,3} would be treated as e = {y:1, x:3}. Supporting this would probably open up a new bundle of problems though. ) It would then enable it the user to switch it around and do a = {y:3, x:1} without a problem. This also makes it a bit safer to write as you don’t need to remember the order of the parameters when writing code.

    • From the code I’ve written so far the automatic gaining of type information (the names) is a critical requirement. The initialization of simple types is otherwise far too complex.

      Your example of ‘a’ having two different values is a compelling reason to not allow out-of-order assignment. Or perhaps its an indicator that out-of-order requires a marker or some kind. Perhaps:

      a = ~e

      This could also signal that an incomplete tuple could be converted, accepting the defaults for unmatched fields.

      The reason I was thinking of unordered matches however is primarily for function calls. There it would be nice to call functions by name, and be able to put the names in any order (Python style). Since internally the concept of params/value-lists and tuples are extremely similar I though perhaps it is a generic concept.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s