How Lapsang avoids two common forms of explicit casting

! Warning: this post hasn't been updated in over three years and so may contain out of date information.

Lapsang logoI have recently been working on how Lapsang might support both static and dynamic composition. In the process of thinking about these topics though, I’ve come to realise what mammoth topics they are. For example, in OO languages, we take for granted that there is a base class – often Object – that can define things like toString(). Because all classes inherit from that base class, all have access to toString(). However, Lapsang does not allow class inheritance. Thus even simple things like a toString() method have to use composition in order to be universally available. So defining how static composition works is taking longer than I expected, and I haven’t even started thinking about dynamic composition! Whilst working on composition, I started thinking about casting and what the syntax might look like in Lapsang. This led me to question whether Lapsang can avoid explicit casting altogether. The conclusion was yes, it can completely avoid explicit casting, sort of…The caveat over explicit casting lies in the realms of information loss. An 8 bit integer value can be assigned to a double for example and it’s guaranteed that there will be no information loss. However, the reverse doesn’t apply. Even for values between -128 and 127 (ie when there is no overflow to handle), if the double value has a fractional part, then that will be lost when assigning the value to an integer. Lapsang needs to be explicitly told that data loss is acceptable in these cases and this is a form of explicit cast. However, no other explicit casting should be required. This article looks at how this can be achieved.

Explicit casting example one: Moving type checking to the runtime
With statically typed languages, the case can arise where the developer knows better than the compiler as to what a variable’s type will be at runtime. In these cases, explicit casting is used to tell the compiler to go easy on the type checking. The following C# snippet shows an example of this:

When assigning dog to animal, no cast is needed as an instance of a class can be assigned to a parent-typed variable. A cast is required to do the reverse though. It works when the code is run, only because animal was set to an instance of Dog. In Lapsang, this simple code example is tricky to replicate exactly, as there is no inheritance:

No cast is required, but the code will only compile if Animal and Dog have the same public signature (the same public properties and the same public methods, with the same signatures), or if there is no loss of information when the implicit cast occurs. Let’s examine those two statements in turn. Because concrete classes cannot be specified in Lapsang code, structural typing is used to determine if a value meeting one interface also meets another interface. If the two interfaces are structurally identical, no data conversion is required. The value meets the needs of the interface, and so is an instance of that interface. To explain the second point – that implicit casting is permissible if no information is lost – we need another code example. To start, let’s define two interfaces:

A common pattern in programming is to want to create one object from the values of another. Let’s assume we want to create an instance of 3DPoint from an instance of rectangular. No value can satisfy both interfaces, as the signatures are different: the toPolar() method exists in just rectangular and 3DPoint has a z property. So imagine we want to create a 3DPoint from the rectangular value manually. The code to create one from the other would be:

Because z has a default value of 0, it doesn’t need to be specified in the initialiser. The above contains some rather pointless boilerplate code however, so Lapsang offers a short-cut way of expressing it:

The two code snippets are functionally identical. Just as we might expect a computer language to support casting an int to a double implicitly, so Lapsang extends that idea to all immutable values. If the value complies with the signature, nothing need happen: the value can be assigned directly. Alternatively, if the value can be represented by the new form without loss of information, then a new value is created and its property values are set from the original value’s properties. Just as with the example of an int and double though, implicit casting is not allowed if there could be information less. So in the latter case, the compiler must be told that information loss is acceptable. Replacing the animal example with the point interfaces, we get:

Line one sets up the initial data, which can be copied implicitly in line two. Line three though involves the value of z being lost, so we use the from keyword to let the compiler know that this is OK. Whilst rect and rect2 likely refer to two separate instances of rectangular, Lapsang determines the equality of immutable values through their property values, therefore rect and rect2 are identical on line four.

Let’s take the example a bit further by introducing a new interface:

The following code further shows Lapsang’s implicit casting at work:

Because a 3DPoint value implements the 2DPoint interface (remember we are using structural typing, so it doesn’t have to explicitly express this fact), no casting occurs on the second line. So xypoint holds a 3DPoint value. Then on the next line, the value of xypoint is checked and found to meet the 3DPoint interface requirements, so again it’s not changed. Thus the value of 4 for xyzpoint2 is preserved.

This idea only works for immutable values. For a mutable value, the very act of reading one of its properties might change its state. Also, creating a new instance of a mutable value may have side effects. Thus for mutable values, implicit casting can only occur via interface matches using structural typing, as this results in no new value being created. However, explicit casting likely makes no sense for such mutable values either, as there is no class inheritance within Lapsang.

Explicit casting example two: type checks have no persistence
Often in a statically typed language, one has to perform a type check and cast to coerce the compiler into accepting the validity of some code. This is demonstrated by the following code example:

Having to write such code irritates me. A test has been performed on list[0], so why must I then have to explicitly cast it? This irritation can be addressed by having the compiler understand the significance of is tests. So the following compiles just fine in Lapsang:

Another example of this feature at play can be shown through reusing the geometry interfaces from before:

Because the if expression starts with xypoint is 3DPoint, the compiler knows that, for the rest of the expression, it can treat xypoint as being an implementation of 3DPoint and so it is valid to access z. This simple feature removes the need for all test/cast situations that other OO languages must handle.

By supporting both implicit value creation for immutable values, as well as an intelligent is function, it should be possible for Lapsang to avoid needing explicit casting completely, save for explicitly handling potential information loss.

7 thoughts on “How Lapsang avoids two common forms of explicit casting

  1. Is there a typo in this?

    val rect = new rectangular { x = 1, y = 2 }
    3dPoint point = rect
    rectangular rect2 = from 3dPoint
    return rect == rect2 // true

    otherwise how do the values 1, 2 get assigned to rect2?

  2. @Owen,

    No, it’s not a typo. Lapsang works on the basis of “statically typed duck typing” or structural typing as it’s also known. The name comes from the saying “if it looks like a duck…” So if two interfaces have properties called x and y, and they have the same type in both interfaces, they are assumed to be equivalent. Thus, one could explicitly copy the values:

    or Lapsang can be left to implicitly copy them. The from keyword is needed in the latter case though as point contains a z property, which gets lost through the implicit copy. The from simply tells the compiler that the loss of the z is acceptable in this case.

  3. Here’s what I understand.

    In the last line, you compare rect and rect2, and to be the equivalent, their immutable values should be the same, e.g. x = 1, y =2.

    In line 2, you create an object using the interface 3dPoint, which implicitly casts the values from rect, so point has the values x=1, y=2, z=0.

    In line 3, I understand that you are explicitly casting to rectangular from the interface 3dPoint, but I don’t see where you tell it what you are casting. How does it know that you want to cast point?

  4. Damn, not only are you right that it’s a typo, I still missed it when you repeated it in your first comment! It should of course have been:

    rectangular rect2 = from point

    Well spotted and thanks. I’ve updated the code.

  5. You have a stray { in your lapsang snippet:

    list list = someMethodGuaranteedToReturnAtLeastOneElement(); if list[0] is exampleInterface2 exampleInterface2 ex = list[0]; // do something with ex }

    It’s because, deep down, you know significant whitespace Is Bad 😉

  6. @mnen,

    You can use <code/> tags to highlight code (at least I can,and I think it’s a feature enabled for anyone).

    I’ve amended your comment for you. And fixed the code, thanks 🙂

Comments are closed.