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.