How Lapsang avoids two common forms of explicit casting
I 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:
// Animal is base class of Dog Dog dog = new Dog(); Animal animal = dog; Dog dog2 = (Dog)Animal;
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:
// Animal and Dog are both interfaces Dog dog = new Dog() Animal animal = dog Dog dog2 = animal
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:
public interface 3DPoint inferred default val double x = 0 val double y = 0 val double z = 0 public interface rectangular default rectangularImpl val double x val double y polar toPolar()
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:
val rect = new rectangular { x = 1, y = 2 }
val 3dPoint = new 3DPoint { x = rect.x, y = rect.y }
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:
val rect = new rectangular { x = 1, y = 2 }
3dPoint point = rect
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:
val rect = new rectangular { x = 1, y = 2 }
3dPoint point = rect
rectangular rect2 = from point
return rect == rect2 // true
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:
public interface 2DPoint inferred default val double x = 0 val double y = 0
The following code further shows Lapsang’s implicit casting at work:
val xyzpoint = new 3DPoint { x = 2, y = 3, z = 4 }
2DPoint xypoint = xyzpoint
3DPoint xyzpoint2 = xypoint
return xyzpoint2.z == 4 // true
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:
List list = SomeMethodGuaranteedToReturnAtLeastOneElement();
if (list[0] is ExampleClass2)
{
ExampleClass2 ex = (ExampleClass2)list[0];
// do something with ex
}
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:
list list = someMethodGuaranteedToReturnAtLeastOneElement(); if list[0] is exampleInterface2 exampleInterface2 ex = list[0]; // do something with ex
Another example of this feature at play can be shown through reusing the geometry interfaces from before:
public interface 2DPoint inferred default
val double x = 0
val double y = 0
public interface 3DPoint extends 2DPoint inferred default
val double z = 0
...
val xyzpoint = new 3DPoint { x = 2, y = 3, z = 4 }
2DPoint xypoint = xyzpoint
if xypoint is 3DPoint && xypoint.z == 4
// this point in the code will be reached
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.
Share This Post...
7 comments so far, click here to read them or add another
7 Comments so far
Leave a reply


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?
@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:
3dPoint point = new 3dPoint { x = rect.x, y = rect.y } rectangular rect2 = new rectangular { x = point.x, y = point.y }or Lapsang can be left to implicitly copy them. The
fromkeyword is needed in the latter case though as point contains azproperty, which gets lost through the implicit copy. Thefromsimply tells the compiler that the loss of thezis acceptable in this case.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?
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 pointWell spotted and thanks. I’ve updated the code.
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
Hmm. How do I indicate code in comments? I do not have teh skillz.
@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