digitalmars.D - An idiom for disabling implicit conversions
- Don (45/45) Mar 19 2010 I've found a pretty cool idiom for disabling pesky implicit conversion.
- bearophile (13/15) Mar 19 2010 Is different from this one?
- Don (7/23) Mar 19 2010 It isn't. It's the same as:
- bearophile (7/8) Mar 19 2010 So this means "all separately instantiated add() functions with T that t...
- Justin Johansson (20/77) Mar 19 2010 The problem is that D does not have any formal semantics for its
- Uranuz (76/76) Aug 26 2014 I revived this topic, because I have similar problem but with
- Uranuz (124/124) Aug 26 2014 OK. Nobody ansvered anything yet. So I was thinking about this
- Uranuz (6/6) Aug 26 2014 In the pre-last paragraph please read text^
- Timon Gehr (11/32) Aug 26 2014 It's not _that_ strong. Also, this is a library problem.
I've found a pretty cool idiom for disabling pesky implicit conversion. This is a situation that I've encountered with BigInt; but I think it's a general problem. I have an "add integer" operation. Let's call it: void add( x ), where x is a built-in integral type. If I define add(long x), everything works great -- it can be called with x as a byte, ubyte, short, ushort, int, uint, or long. Well, almost. Unfortunately, it doesn't work for ulong: add(ulong.max) will either fail to compile, or get implicitly cast to add(-1)!! You can fix this by defining add(ulong x) as a special case. Now x can be long or ulong, and it works. Great! Problem is, now if x is an integer, the compiler complains that it's ambiguous -- should it call the long or the ulong version? Bummer! You could solve that by creating int, uint, short, ushort,... functions. Eight in total, but seven of them are identical. Massive source code duplication. So use a template: void add(T)(T x) { // everything except ulong } void add(T : ulong)(T x) { } Problem solved! Well, not quite. Now the source code duplication is gone, but you still have template bloat: there are still 8 functions in the executable. Then consider void evenworse(x, y) where both x AND y need special treatment if they are ulong. 8*8 = 64 functions go into the executable, but only 4 of them are different! Ouch. If only there was a way to disable implicit conversions... void add()(long x) { assert(x == 7); } void add(Tulong)(Tulong x) if ( is(Tulong == ulong) ) { assert(x == 6); } void main() { add(7L); add(6UL); add(7); // Look ma, no conflicts! No bloat! } Template constraints are seriously cool.
Mar 19 2010
Don: Do you know why this semantics:void add(Tulong)(Tulong x) if ( is(Tulong == ulong) )Is different from this one? void add(Tulong:ulong)(Tulong x) { This page says: http://www.digitalmars.com/d/2.0/templates-revisited.html T:int, // T must be int type So it seems the same as the version with the template constraint. Can't the D2 semantics of (Tulong:ulong) be redefined to become the same of the version with template constraint if(is(Tulong==ulong)) to remove this special case from D?Template constraints are seriously cool.I agree :-) And Andrei has used them a lot in Phobos2. Thank you for your trick (but but the idea is to have a language that needs as few tricks as possible). Bye, bearophile
Mar 19 2010
bearophile wrote:Don: Do you know why this semantics:It isn't. It's the same as: add(ulong x) which leads to an ambiguity with add(long x). Template constraints are considerably more powerful than template specialisation.void add(Tulong)(Tulong x) if ( is(Tulong == ulong) )Is different from this one? void add(Tulong:ulong)(Tulong x) { This page says: http://www.digitalmars.com/d/2.0/templates-revisited.html T:int, // T must be int type So it seems the same as the version with the template constraint.Can't the D2 semantics of (Tulong:ulong) be redefined to become the same of the version with template constraint if(is(Tulong==ulong)) to remove this special case from D?Not without loss of capability.
Mar 19 2010
Don:Not without loss of capability.So this means "all separately instantiated add() functions with T that the compiler can automatically cast to uint": void add(T:ulong)(T x) { So it's something very different from the template constraint, that just accepts/refuses to see the template according to the if clause. I have programmed D1 for years thinking add(T:ulong) means something different, a single instantiation with ulong. I was wrong. Thank you, as usual, bearophile
Mar 19 2010
The problem is that D does not have any formal semantics for its informal type system (this, of course, being a tautological statement). The discovery of any "cool idiom" for disabling "pesky implicit conversion" is tantamount to engaging the same sorts of hacks that web programmers do with CSS (Cascading Style Sheets). Need I elaborate? People might scoff as they surely will but to even begin to understand formalism in data types this would be a good place to start ... W3C XML Schema Definition Language (XSD) 1.1 Part 2: Datatypes http://www.w3.org/TR/2009/WD-xmlschema11-2-20091203/ Editors (Version 1.1): David Peterson, invited expert (SGMLWorks!) <davep iit.edu> Ashok Malhotra, Oracle Corporation <ashokmalhotra alum.mit.edu> C. M. Sperberg-McQueen, Black Mesa Technologies LLC <cmsmcq blackmesatech.com> Henry S. Thompson, University of Edinburgh <ht inf.ed.ac.uk> Regards Justin Johansson Don Wrote:I've found a pretty cool idiom for disabling pesky implicit conversion. This is a situation that I've encountered with BigInt; but I think it's a general problem. I have an "add integer" operation. Let's call it: void add( x ), where x is a built-in integral type. If I define add(long x), everything works great -- it can be called with x as a byte, ubyte, short, ushort, int, uint, or long. Well, almost. Unfortunately, it doesn't work for ulong: add(ulong.max) will either fail to compile, or get implicitly cast to add(-1)!! You can fix this by defining add(ulong x) as a special case. Now x can be long or ulong, and it works. Great! Problem is, now if x is an integer, the compiler complains that it's ambiguous -- should it call the long or the ulong version? Bummer! You could solve that by creating int, uint, short, ushort,... functions. Eight in total, but seven of them are identical. Massive source code duplication. So use a template: void add(T)(T x) { // everything except ulong } void add(T : ulong)(T x) { } Problem solved! Well, not quite. Now the source code duplication is gone, but you still have template bloat: there are still 8 functions in the executable. Then consider void evenworse(x, y) where both x AND y need special treatment if they are ulong. 8*8 = 64 functions go into the executable, but only 4 of them are different! Ouch. If only there was a way to disable implicit conversions... void add()(long x) { assert(x == 7); } void add(Tulong)(Tulong x) if ( is(Tulong == ulong) ) { assert(x == 6); } void main() { add(7L); add(6UL); add(7); // Look ma, no conflicts! No bloat! } Template constraints are seriously cool.
Mar 19 2010
I revived this topic, because I have similar problem but with additional requirements. I have interface and some classes that inherits from it. Also I have property two overloads of *set* properties with types int and Nullable!int There is listing of code that illustrates funny bug that I had in my project. import std.stdio, std.typecons; interface ListControl { property { void selValue(int value); void selValue(Nullable!int value); } } class CheckBoxList: ListControl { override property { void selValue(int value) { writeln("value: int"); } void selValue(Nullable!int value) { writeln("value: Nullable!int"); } } } void main() { //Someone considered to make it of byte type //instead of int to save memory Nullable!byte myValue; //it is null/uninitialized //Creating object auto checkBoxList = new CheckBoxList; //Let's assign value to our property //You see that types don't match. But let's imagine that it is a complicated project //and class implementation and *myValue* located in different modules so programmer don't remember right type checkBoxList.selValue = myValue; //This just silently fails in runtime without some compile-time error } I spend some time to localize it. I know the source of problem, so if you haven't guessed it I'll explain. std.typecons.Nullable uses *alias this* to implicitly access payload of Nullable. I like it, because it makes code shorter and allows to work with Nullable like it is underlying value (I'm not sure about how this sentence sounds in English). But... but... When using it as property it provides TOO MUCH implicit castings for me where I not expect it be. So in the listing assignment doesn't call Optional!int overload of property (types don't match), but instead it implicitly calls get of Nullable. It's return value is byte. Byte is implicitly convertible to int. The only problem that myValue has *Null* state and get fails with assertion. It is not what programmer wants to see as a result. The worst case he expects is compile-time error about type mismatch. But it is not all! In this topic solution to it is to use cure-all template methods that will help to know *real* type of *myValue*. But... but... As you see in the example above I want to have *virtual* methods, that can't be of template nature in D. So this solution doesn't work there. Is there in D the other way to say that I want only direct/explicit (I don't know how express it correctly in English) type match in virtual property method without using templates? Is there some *explicit* keyword (I'm joking). C++11 has explicit keyword but it's purpose is different from what I'm saying. It's interesting to see any ideas, because I have doubts of using Nullable and *alias this* feature because of bugs like this.
Aug 26 2014
OK. Nobody ansvered anything yet. So I was thinking about this problem for a while so I figured out possible soulution. What I can do in this situation in to forbid these opertions and make compiler issue an error during compilation or implement functionality for all types that implicitly convertible to int. But what I don't like about this idea is if I forget about some type or something will change in the future it can become failing in runtime but will issue an error compile-time. Also I need to implement a lot of methods. In my real project ListControl is interface template so CheckListBox is also class template. import std.stdio, std.typecons, std.traits, std.typetuple; interface ListControl(ValueType) { property { void selValue(ValueType value); void selValue(Nullable!ValueType value); static if( is( ValueType == int ) ) { mixin(genSetSelValueDecls(IntCastedTypes)); } else static if( is( ValueType == uint) ) { //Declaration for uint type } } } enum IntCastedTypes = ["bool", "byte", "ubyte", "short", "ushort", "char", "wchar"]; string genSetSelValueDecls(string[] types) { string result; foreach( type; types ) result ~= ` void selValue(Nullable!` ~ type ~ ` value); `; return result; } string genSetSelValueMethods(string[] types) { string result; foreach( type; types ) result ~= ` void selValue(Nullable!` ~ type ~ ` value) { writeln("value: ", typeof(value).stringof); } `; return result; } class CheckBoxList(ValueType): ListControl!(ValueType) { override property { void selValue(ValueType value) { writeln("value: ", typeof(value).stringof); } void selValue(Nullable!ValueType value) { writeln("value: ", typeof(value).stringof); } static if( is( ValueType == int ) ) { mixin(genSetSelValueMethods(IntCastedTypes)); } else static if( is( ValueType == uint) ) { //implementation for uint type } } } void main() { Nullable!ubyte myValue; //it is null/uninitialized //Creating object auto checkBoxList = new CheckBoxList!int; checkBoxList.selValue = myValue; } So this is working and does what is expected to do. Although is should be unittested pretty well to eliminate all sorts of bugs. Types that implicitly convertible to int were taken from: http://dlang.org/type.html (IntegerPromotions) But what I noticed is that uint type is implicitly convertible to int to. So this is not covered by example because it is not documented case. May be it is just nasty bug in compiler (I don't know but I think it is). I thing this is a bug because uint.max cannot be correctly represented in int and shouldn't be implicitly convertibe. I raised problems with conversions between integer types a time ago but it had no result or resolution. And it's strange for me that so trivial aspects of language become source of nasty bugs. For example: import std.stdio; void main() { uint a = uint.max; writeln(a); // 4294967295 int b = a; writeln(b); // -1 } I don't like the situation that when implementing new feature in my library I must fight the compiler in order to get what obviously expected from it. I like the language but lots of small bugs makes me get upset. Please fix integers! I want to use all of them: ubyte, long, short. Not only int that is considerer as priorite for some reason that I don't understand. I don't thing that compatibility with C/C++ reasons should create such a mess about integer type casts. I haven't such problems whaen I was programming in C/C++. Also notice that C is language with weak type system but D is declared to have type system. So rules should be slightly different. Of course all of the above is just my own opinion and language designers may do whatever they like. But...
Aug 26 2014
In the pre-last paragraph please read text^ Also notice that C is language with weak type system but D is declared to have type system. as: Also notice that C is language with weak type system but D is declared to have *strong* type system.
Aug 26 2014
On 08/26/2014 05:46 PM, Uranuz wrote:In the pre-last paragraph please read text^ Also notice that C is language with weak type system but D is declared to have type system. as: Also notice that C is language with weak type system but D is declared to have *strong* type system.It's not _that_ strong. Also, this is a library problem. I'd suggest you file a bug report/enhancement request against Phobos, if it is not already there. https://issues.dlang.org/ It seems that adding some constructor(s)/opAssign(s) to Nullable will solve this problem. ( BTW: You are more likely to get a quick response if your post looks a little bit more digestible, like: On 08/26/2014 05:38 PM, Uranuz could have written:// --- import std.typecons; void main(){ Nullable!ubyte x; Nullable!int y=x; // AssertError: Called 'get' on null Nullable!ubyte } // --- import std.typecons; void main(){ Nullable!ubyte x; Nullable!int y; y=x; // AssertError: Called 'get' on null Nullable!ubyte } // --- Is this a bug?Or you could have gone straight to https://issues.dlang.org/. :) )
Aug 26 2014
On Tuesday, 26 August 2014 at 16:48:08 UTC, Timon Gehr wrote:On 08/26/2014 05:46 PM, Uranuz wrote:The last example is even more obvious but didn't come to my mind. Maybe I'l think of better implementation myself and push something to standard library. The best way to make something working is to do it myself [jokingly]In the pre-last paragraph please read text^ Also notice that C is language with weak type system but D is declared to have type system. as: Also notice that C is language with weak type system but D is declared to have *strong* type system.It's not _that_ strong. Also, this is a library problem. I'd suggest you file a bug report/enhancement request against Phobos, if it is not already there. https://issues.dlang.org/ It seems that adding some constructor(s)/opAssign(s) to Nullable will solve this problem. ( BTW: You are more likely to get a quick response if your post looks a little bit more digestible, like: On 08/26/2014 05:38 PM, Uranuz could have written:// --- import std.typecons; void main(){ Nullable!ubyte x; Nullable!int y=x; // AssertError: Called 'get' on null Nullable!ubyte } // --- import std.typecons; void main(){ Nullable!ubyte x; Nullable!int y; y=x; // AssertError: Called 'get' on null Nullable!ubyte } // --- Is this a bug?Or you could have gone straight to https://issues.dlang.org/. :) )
Aug 26 2014
On Tuesday, 26 August 2014 at 18:29:49 UTC, Uranuz wrote:On Tuesday, 26 August 2014 at 16:48:08 UTC, Timon Gehr wrote:If I could write multiple alias this in Nullable I could create implicit conversions from Nullable!byte not only to byte but also to Nullable!short, Nullable!int. In this case there is no need to implement lots of overloads for property of type Nullable!int in order to correctly accept Nullable!byte, Nullable!ubyte and so on. I have wrote an example code and seems that only fixing Nullable implementation will not help to eliminate the problem I was talking about. So I have a choice: 1. Implement Nullable type without *alias this* at all (and access to internal value with getter method or property) 2. Implement lots of overloads of virtual Nullable!int property to accept every minor integer types. Also is needed to forbid Nullable!uint conversion because it is incorrect. I can't correctly store uint.max inside int variable, but implicit coversion is allowed in the compiler (and it is not documented). Because using alias this makes code shorter I prefer it, although it is very tricky feature and needs attention during development.On 08/26/2014 05:46 PM, Uranuz wrote:The last example is even more obvious but didn't come to my mind. Maybe I'l think of better implementation myself and push something to standard library. The best way to make something working is to do it myself [jokingly]In the pre-last paragraph please read text^ Also notice that C is language with weak type system but D is declared to have type system. as: Also notice that C is language with weak type system but D is declared to have *strong* type system.It's not _that_ strong. Also, this is a library problem. I'd suggest you file a bug report/enhancement request against Phobos, if it is not already there. https://issues.dlang.org/ It seems that adding some constructor(s)/opAssign(s) to Nullable will solve this problem. ( BTW: You are more likely to get a quick response if your post looks a little bit more digestible, like: On 08/26/2014 05:38 PM, Uranuz could have written:// --- import std.typecons; void main(){ Nullable!ubyte x; Nullable!int y=x; // AssertError: Called 'get' on null Nullable!ubyte } // --- import std.typecons; void main(){ Nullable!ubyte x; Nullable!int y; y=x; // AssertError: Called 'get' on null Nullable!ubyte } // --- Is this a bug?Or you could have gone straight to https://issues.dlang.org/. :) )
Aug 27 2014