Wednesday, August 13, 2014

Test of links etc.

This is a test.  How will this work?  What can I do with this here?

G C 545 This is a test of an Alert.

Tuesday, August 12, 2014

This is a little script piece which should access the main script. That was a little script piece.

Tuesday, September 3, 2013

Axioms of Implicit Type Conversion

Both Java and .NET publish guidelines about when types should define conversion to various other types. Unfortunately, the actual combinations of implicit type conversions allowed by the frameworks themselves don't seem to have followed any clear systematic rationale.  The fundamental cause of this, I suspect, stems from the requirement that implicit type conversions which allowable in any context must generally be allowable in every conceivable context, when there are at least three different kinds of conversions which would make sense as implicit conversions in different contexts.  There are many axioms which should be applicable to all implicit conversions, but unless one wants to forbid all implicit conversions among floating-point types in all circumstances, the only way to make important axioms hold is to allow implicit floating-point conversions in some circumstances while disallowing them in others.

I would posit that four of the most important axioms related to implicit conversion would be:
  1. If x is some abstract quantity, Tx is the T which represents it best (not necessarily exactly), and T is implicitly convertible to U, then "(U)Tx" should yield either the U which best represents that same abstract quantity, or something essentially as good.  The implicit conversion from "float->double" violates this axiom; conversions from "Decimal->double" or "double->float" would not
  2. For any instance Tx of type T, if T is implicitly convertible to U and U to V, then (V)(U)Tx should yield (V)Tx.  This axiom fails for e.g. long->float->double.  If implicit float->double were eliminated, this axiom could apply even if "Decimal->double" and "double->float" were added [though the addition of other types which were convertible to "double" might violate it].  Note also that proper behavior of this axiom would require that conversion from longer types directly to float be performed as though the value were first cast to double. This would mean that (float)someLong might not yield the float which was closest to someLong, but the difference between that and the closest float would be negligible by "float" standards.
  3. Given instances Tx and Ty of any type T, and any type U, the expression "((U)Tx == Ty) == (Tx == Ty)" should refuse to compile, throw an exception in the typecast to U, or else return true.  All floating-point conversions will violate this axiom if the operands of "==" are implicitly convertible.
  4. Given any mix of variables of any types, if "a==b", "b==c", and "a==c" all compile, and if any two are true, the third must be as well.  The implicit conversions from "int->float" and "long->double" violate this axiom, as does (for .NET) the implicit conversion from `char[]` to `String`.  This is the most significant of the axioms which would be violated by "double->float".
I suspect that the motivation for allowing implicit float->double while disallowing double->float was the fact that the former is compatible with axiom #4 while the latter is not.  I would posit, however, that the proper way to make axiom #4 hold is not to disallow implicit double->float conversions everywhere, but rather to recognize that there are some contexts (such as the "==" operator) where no implicit conversions involving floating-point numbers should be allowed.  Disallowing mismatched operands to "==" would satisfy axiom #3 and #4.  Conversely, allowing "==" to engage in any implicit conversion whose reverse conversion is not reversible violate #3.

What makes the decision to choose conversion rules based upon axiom #4 even more unfortunate is that its main effect is to help the compiler disambiguate code which the compiler should regard as ambiguous.  It's far from clear whether a programmer who writes "someFloat == someDouble" wants to check whether "someFloat" is the best "float" representation of "someDouble", or whether "someDouble" is the best "double" representation of the nominal value of "someFloat" (which may or may not bear much relation to the number that float is supposed to represent).  It's true that only the latter test implements an equivalence relation, but that doesn't imply that it's what the programmer's actually trying to test.

The combination of allowing implicit float->double but forbidding double->float means that code gets cluttered with lots of typecasts in cases where the behavior of the code in the absence of typecasts would be clearer than its behavior with, and yet code which is almost certainly wrong compiles without complaint.  Consider, for example, which of these is more worthy of a compiler warning:

    float oneTenthFloat = 1.0/10.0; // Assume this compiled as though the typecast were present
    double oneTenthDouble =1.0f/10.0f;

The obvious intention of the the first statement would be to set oneTenthFloat to the "float" value which is closest to 1/10, i.e. 13,421,773/134,217,728.  That's precisely what the statement would do if implicit casts were legal in such case, and there would be no plausible reason for the programmer to expect anything else.  The second statement in C would set oneTenthDouble to the "double" value best represents 1/10; it's off by 5.6E-18.  In C# it sets it to a "double" value which is off by 1.4E-9, i.e. more than 268 million times as much.

While there are limits to what can be done to .NET at this stage, there are some changes that could help.  For example, a language could include new compile-time-only types which would be run-time equivalents to the existing primitives, but had different implicit casting behaviors (much the way "Dynamic" is really "Object" under the hood).  Code which as written to use the new types could benefit from the improved semantics and diagnostics, while existing code would continue to run as it always has.  The types and some of their conversion operators could be tagged to indicate that their use should be forbidden in cases where a value of the original type is passed the nth parameter of a function, and the function has an overload with the appropriate number of parameters whose nth parameter can take either the original type (without regard for whether that overload would fit the types of any other parameters).

Friday, October 15, 2010

Thoughts about (object) cloning

One commonly-useful method for objects to support is cloning. Unfortunately, cloning tends to pose many difficulties in object-oriented languages, since the type of object returned by the clone method will vary depending upon the type of object performing it, and since base types may be used to derive some types that allow cloning and some that do not.

As I see it, there are four types of classes when it comes to cloning:
  1. Those that simply cannot be cloned
  2. Those that are perfectly happy if MemberwiseClone'd, but don't promise that their descendant classes will support cloning.
  3. Those that can be meaningfully cloned, but they require more than MemberwiseClone, and they don't promise that their descendants will support cloning.
  4. Those that can be cloned, and promise such ability on behalf of themselves and any descendant classes.

Unfortunately, the .net framework really doesn't do much to indicate which types fall in which category. My proposed pattern would make things much clearer, and would look something like this:

Class SampleSemicloneableBase
Enum DontTryThis
NoReallyDont = 0
End Enum

Public SampleArray1(0) As Integer

Protected Overridable Function CloneBase() As Object
Dim Result As SampleSemicloneableBase
Result = CType(MyBase.MemberwiseClone(), SampleSemicloneableBase)
Result.SampleArray1 = Result.SampleArray1.Clone
Return Result
End Function

Protected Shadows WriteOnly Property MemberwiseClone As DontTryThis
Set(ByVal value As DontTryThis)
Throw New NotSupportedException()
End Set
End Property
End Class

Class CloneableDerived
Inherits SampleSemicloneableBase
Public SampleArray2(0) As Integer

Public Shadows Function Clone() As CloneableDerived
Return CType(Me.CloneBase, CloneableDerived)
End Function

Protected Overrides Function CloneBase() As Object
Dim Result As CloneableDerived
Result = CType(MyBase.CloneBase(), CloneableDerived)
Result.SampleArray2 = Result.SampleArray2.Clone
Return Result
End Function

Public Overrides Function ToString() As String
Return SampleArray1(0).ToString & "." & SampleArray2(0).ToString
End Function
End Class

Class NonCloneableDerived
Inherits SampleSemicloneableBase
Protected Shadows WriteOnly Property CloneBase As DontTryThis
Set(ByVal value As DontTryThis)
Throw New NotSupportedException()
End Set
End Property
End Class

Class cloningTest
Shared Sub test()
Dim thing1 As New CloneableDerived
thing1.SampleArray1(0) = 1
thing1.SampleArray2(0) = 2
Dim thing2 As CloneableDerived = thing1.Clone
Debug.Print(thing1.ToString & "/" & thing2.ToString)
thing2.SampleArray1(0) = 3
thing2.SampleArray2(0) = 4
Debug.Print(thing1.ToString & "/" & thing2.ToString)
End Sub
End Class

For this example, the base class has one field which needs to be cloned, and the cloneable derived class adds another. Each cloneable derived class should declare a "Public Shadows Function Clone()" of its own type, which should call the overridable "CloneBase()" and typecast the result as appropriate. Each cloneable class which needs to do anything when cloned other than call its parent's cloning method should add an override to CloneBase, and that override should start by calling it's parent's CloneBase method.

Classes which aren't going to support cloning-related methods of their parents (e.g. MemberwiseClone in the case of SampleSemicloneableBase, or CloneBase in the case of NonCloneableDerived) should seal off those methods from protected classes; the implementation here is perhaps a little cutesy, but should be pretty good (Intellisense should set people straight if they try it).

Classes which follow my pattern can be readily classified into types #1, #3, and #4. Type #2 is a bit dodgy; classes which follow my pattern would not be type #2 (they'd be #3 even if all they do is call Object.MemberwiseClone) but there's no way of knowing whether a type which supports MemberwiseClone really supports it, or simply hasn't disabled it. But for types which follow the pattern, the cloning status would be readily apparent.