digitalmars.D - implicit construction operator
- WebFreak001 (73/73) Feb 26 2018 hi, I had an idea from using some C# which I think would be
- ketmar (4/14) Feb 26 2018 please no. such unobvious type conversions goes out of control really fa...
- aliak (19/37) Feb 26 2018 How if you don't mind me asking? Seems like many features can go
- ketmar (3/4) Feb 26 2018 no, it is not ok. it was a mistake. it is now too late to fix it, but we...
- aliak (3/8) Feb 26 2018 Oops, yeah, ok, D char ... char -> int yeah mistake.
- ketmar (4/7) Feb 26 2018 library authors can create overloads for various argument types. with
- aliak (2/10) Feb 26 2018 Touche.
- Nick Treleaven (4/12) Feb 28 2018 No, we do not. Presumably you're not suggesting every function
- TheFlyingFiddle (11/18) Feb 26 2018 I would be very happy if char -> int and int -> float was not
- H. S. Teoh (18/37) Feb 26 2018 Yeah, implicit char -> int is totally evil, and should never have gotten
- TheFlyingFiddle (4/8) Feb 27 2018 Yes, your right, did not really think that through. Also if you
- Rubn (28/46) Feb 27 2018 The bigger mistake is D allowing implicit conversion when a type
- Meta (47/58) Feb 26 2018 This is possible in the language today using the implicit class
- Nick Treleaven (30/59) Mar 02 2018 Problems:
- biozic (9/27) Feb 27 2018 Such implicit conversion was once 'on the table', see this
really cool in D. Basically allow modifying implicit construction of a type. Right now we only have struct opAssign/constructor implicit conversion, but this addition would also add it to classes and make it even more convenient. Changes in code (example Nullable): --- struct Nullable(T) { this(typeof(null)) implicit {} // <-- implicit is new } void foo(Nullable!int a, Nullable!int b) {} --- Now before you would have only been able to do this: --- Nullable!Foo a; foo(a, Nullable!int(5)); --- but now you should also be able to do: --- Nullable!Foo x = null; Nullable!Foo y = 5; foo(null, 5); // while this would be nice, I am not sure how well this is possible right now: Nullable!int[] foo = [1, 2, 3, null, 5]; --- The array syntax might not be possible if it looks at consistency inside the array before attempting to cast it, but for function arguments and quicker This is especially making this case less of a pain to deal with: --- void sendMessage(string content, Nullable!Snowflake nonce = Nullable!Snowflake.init, Nullable!Embed embed = Nullable!Embed.init); Nullable!Embed embed = Embed.init; sendMessage("a", Nullable!Snowflake.init, embed) // -> void sendMessage(string content, Nullable!Snowflake nonce = null, Nullable!Embed embed = null); sendMessage("a", null, Embed.init); --- Now this would be really useful for Variant: --- struct Variant { this(U)(U value) implicit { ... } } void bar(Variant x, Variant y) {} Variant[] myObjects = [1, 2, "abc", new Node()]; Variant a = 4; bar(4, "asdf"); --- Just a few examples where it could be used in phobos, especially when passing them function arguments: Variant, JSONValue, eventual XML node, Nullable, BigInt, Unique, scoped, NullableRef, Rebindable, AsciiString, Latin1String, etc and many more. Plus also many libraries will be able to make use of this like vibe.d Json, Bson or msgpack or dyaml etc etc etc. Additionally this will make many duplicate functions which are just there for emulating this behaviour disappear, std.regex could accept just regex as argument and strings will be implicitly cast to it, reducing template/overload usage and possibly also making it faster to import. What's your opinion on this? I think it is really useful to have when interoping with scripting languages or doing (de)serialization or for these attribute wrappers which are there because of no function attribute UDAs. I was looking into implementing this to dmd myself but the code on the implicit cast checks looks a bit hardcoded for the types that are in right now and not really suitable to implement this, but maybe someone with more knowledge about dmd internals can do this with ease. This is only a single direction T -> Wrapper and I think this would actually be enough, but it would maybe be possible to add implicit to opCast in the future.
Feb 26 2018
WebFreak001 wrote:Now before you would have only been able to do this: --- Nullable!Foo a; foo(a, Nullable!int(5)); --- but now you should also be able to do: --- Nullable!Foo x = null; Nullable!Foo y = 5; foo(null, 5);please no. such unobvious type conversions goes out of control really fast. there is a reason why D doesn't have such thing, this is not an oversight, but a design decision.
Feb 26 2018
On Monday, 26 February 2018 at 19:32:44 UTC, ketmar wrote:WebFreak001 wrote:How if you don't mind me asking? Seems like many features can go out of control if used incorrectly, but that doesn't mean they're a bad idea. I can understand they're a slippery slope, but it seems some people think it's a completely bad idea. It makes libraries *much* more intuitive and expressive (C++ just got it wrong by choosing the wrong default). If you allow library authors to opt in instead of opt out then it becomes a conscious decision for one. But at the very least, shouldn't we put some thought in to allowing it for inherent sub-type relationships. Where an alias this is used, there's probably such a relationship. BigInt to int is another one. Algebraic types as mentioned falls under that category as well. And if that's also a no no, how about char -> int. Or int -> float? Is ok? Maybe there're some valid arguments, to disallow it *completely* though? CheersNow before you would have only been able to do this: --- Nullable!Foo a; foo(a, Nullable!int(5)); --- but now you should also be able to do: --- Nullable!Foo x = null; Nullable!Foo y = 5; foo(null, 5);please no. such unobvious type conversions goes out of control really fast. there is a reason why D doesn't have such thing, this is not an oversight, but a design decision.
Feb 26 2018
aliak wrote:And if that's also a no no, how about char -> int. Or int -> float? Is ok?no, it is not ok. it was a mistake. it is now too late to fix it, but we can avoid doing more of the same kind.
Feb 26 2018
On Monday, 26 February 2018 at 21:34:21 UTC, ketmar wrote:aliak wrote:Oops, yeah, ok, D char ... char -> int yeah mistake. int -> long even?And if that's also a no no, how about char -> int. Or int -> float? Is ok?no, it is not ok. it was a mistake. it is now too late to fix it, but we can avoid doing more of the same kind.
Feb 26 2018
aliak wrote:On Monday, 26 February 2018 at 21:34:21 UTC, ketmar wrote:can you see a difference between *charactes* and *integers*? i can.aliak wrote:Oops, yeah, ok, D char ... char -> int yeah mistake. int -> long even?And if that's also a no no, how about char -> int. Or int -> float? Is ok?no, it is not ok. it was a mistake. it is now too late to fix it, but we can avoid doing more of the same kind.
Feb 27 2018
On Tuesday, 27 February 2018 at 13:36:30 UTC, ketmar wrote:aliak wrote:Yes I can. That's why I said it was a mistake. A BigInt -> Int would also be integers. But a f(BigInt i) cannot be called with f(3) where as f(long i) can. So not sure what your point is.On Monday, 26 February 2018 at 21:34:21 UTC, ketmar wrote:can you see a difference between *charactes* and *integers*? i can.aliak wrote:Oops, yeah, ok, D char ... char -> int yeah mistake. int -> long even?And if that's also a no no, how about char -> int. Or int -> float? Is ok?no, it is not ok. it was a mistake. it is now too late to fix it, but we can avoid doing more of the same kind.
Feb 27 2018
aliak wrote:It makes libraries *much* more intuitive and expressive (C++ just got it wrong by choosing the wrong default). If you allow library authors to opt in instead of opt out then it becomes a conscious decision for one.library authors can create overloads for various argument types. with `foo(T...) (T args)` it is possible to generate conversions with CTFE. so we *already* have such feature.
Feb 26 2018
On Monday, 26 February 2018 at 21:36:49 UTC, ketmar wrote:aliak wrote:Touche.It makes libraries *much* more intuitive and expressive (C++ just got it wrong by choosing the wrong default). If you allow library authors to opt in instead of opt out then it becomes a conscious decision for one.library authors can create overloads for various argument types. with `foo(T...) (T args)` it is possible to generate conversions with CTFE. so we *already* have such feature.
Feb 26 2018
On Monday, 26 February 2018 at 21:36:49 UTC, ketmar wrote:aliak wrote:No, we do not. Presumably you're not suggesting every function ever written has to be a template, and has to manually support every custom type ever written's chosen implicit conversions?It makes libraries *much* more intuitive and expressive (C++ just got it wrong by choosing the wrong default). If you allow library authors to opt in instead of opt out then it becomes a conscious decision for one.library authors can create overloads for various argument types. with `foo(T...) (T args)` it is possible to generate conversions with CTFE. so we *already* have such feature.
Feb 28 2018
On Monday, 26 February 2018 at 21:30:09 UTC, aliak wrote:On Monday, 26 February 2018 at 19:32:44 UTC, ketmar wrote:I would be very happy if char -> int and int -> float was not implicit. This has happened to me enough times that i remember it: float a = some_int / some_other_int; //ops i don't think this was what I wanted. constrast float a = some_int.to!float / some_other_int.to!float; //ugly but at-least its clear. Not really a big deal (and auto kind of ruins it) but it would make stuff consistent between user types and built in ones.WebFreak001 wrote:And if that's also a no no, how about char -> int. Or int -> float? Is ok? Maybe there're some valid arguments, to disallow it *completely* though? Cheers
Feb 26 2018
On Mon, Feb 26, 2018 at 09:45:03PM +0000, TheFlyingFiddle via Digitalmars-d wrote:On Monday, 26 February 2018 at 21:30:09 UTC, aliak wrote:Yeah, implicit char -> int is totally evil, and should never have gotten into the language in the first place. To wit: void foo(dchar ch) { backup_files(); } void foo(byte b) { format_disk(); } foo('a'); // guess which one gets called.On Monday, 26 February 2018 at 19:32:44 UTC, ketmar wrote:I would be very happy if char -> int and int -> float was not implicit.WebFreak001 wrote:And if that's also a no no, how about char -> int. Or int -> float? Is ok? Maybe there're some valid arguments, to disallow it *completely* though? CheersThis has happened to me enough times that i remember it: float a = some_int / some_other_int; //ops i don't think this was what I wanted. constrast float a = some_int.to!float / some_other_int.to!float; //ugly but at-least its clear.Actually, since operations involving float are "infectious", all you need is: float a = some_int.to!float / some_other_int;Not really a big deal (and auto kind of ruins it) but it would make stuff consistent between user types and built in ones.Not sure what you mean here. In a user type, if opBinary!"/" returns an int, then you still have the same problem. T -- He who laughs last thinks slowest.
Feb 26 2018
On Monday, 26 February 2018 at 23:33:48 UTC, H. S. Teoh wrote:Yes, your right, did not really think that through. Also if you overload opAssign you get implicit conversions in assign expressions...Not really a big deal (and auto kind of ruins it) but it would make stuff consistent between user types and built in ones.Not sure what you mean here. In a user type, if opBinary!"/" returns an int, then you still have the same problem.
Feb 27 2018
On Monday, 26 February 2018 at 19:32:44 UTC, ketmar wrote:WebFreak001 wrote:The bigger mistake is D allowing implicit conversion when a type is declared, with no way to disable it when it wasn't asked for. struct SomeStruct { this(int) { } } SomeStruct value = 10; // I didn't want this int oops(); SomeStruct value2 = oops(); // Didn't want this either. SomeStruct ok() { return SomeStruct(10); } SomeStruct value2 = ok(); // this is what I wanted On the other hand I want to create a "null" like value for my own types. This currently isn't possible in D, because there is no implicit conversion allowed for structs. You can be explicit and declare every type, but "null" wouldn't be as convenient if you had to specify the type it has to convert to every time you wanted to use it. I can only imagine the hell hole DMD's source code would be if that was the case, with its excessive use of null. You can make the same argument about mixin's, it's possible to create some really nasty code with it. That is unreadable and unmaintainable, it's especially a pain in the ass when you have to debug such code. Yet, there it is in D. Most useful features have the capability of being misused, that doesn't mean we shouldn't use them.Now before you would have only been able to do this: --- Nullable!Foo a; foo(a, Nullable!int(5)); --- but now you should also be able to do: --- Nullable!Foo x = null; Nullable!Foo y = 5; foo(null, 5);please no. such unobvious type conversions goes out of control really fast. there is a reason why D doesn't have such thing, this is not an oversight, but a design decision.
Feb 27 2018
On Monday, 26 February 2018 at 19:25:06 UTC, WebFreak001 wrote:Now this would be really useful for Variant: --- struct Variant { this(U)(U value) implicit { ... } } void bar(Variant x, Variant y) {} Variant[] myObjects = [1, 2, "abc", new Node()]; Variant a = 4; bar(4, "asdf"); ---This is possible in the language today using the implicit class construction feature of runtime variadic arrays: class VArray { Variant[] va; this(T...)(T ts) { foreach(t; ts) { va ~= Variant(t); } } } void test(VArray ta...) { foreach (v; ta.va) { writeln(v.type); } } void main() { test(1, "asdf", false); }What's your opinion on this?This is a very slippery slope to fall down. Even `alias this` is pushing the limit of what I think we should allow. That said, there is exactly 1 case where I really, really want some kind of implicit conversion: struct Success {} struct Error { string msg; } alias Result = Algebraic!(Success, Error); Result connectToHost(IPAddress host) { //do some stuff if (operationSucceeded) { return Success(); } else { return Error(statusMessage); } } This currently doesn't work, and you instead have to return Result(Success()) and Result(Error(statusMessage)). I would love to have some way of implicitly constructing an Algebraic from any of the possible underlying types. It would bring us very close (if not all the way) to having Algebraic work identically to sum types in other languages such as Rust, Swift, Haskell, etc. Having to explicitly wrapping the values in an Algebraic doesn't seem like a big deal, but it makes it really annoying to use it in everyday code.
Feb 26 2018
On Monday, 26 February 2018 at 21:07:52 UTC, Meta wrote:This is possible in the language today using the implicit class construction feature of runtime variadic arrays: class VArray { Variant[] va; this(T...)(T ts) { foreach(t; ts) { va ~= Variant(t); } } } void test(VArray ta...)...test(1, "asdf", false);Problems: * The author of `test` has to specifically design support for this to work. `test` might be in a library outside the control of the programmer. * `test` might be part of a template such as a type constructor taking a type as a template parameter - you can't pass e.g. VArray as the type because `test` wouldn't have the ... ellipsis - if it did then `test` would be variadic when the type was an array (which would be inconsistent). * If there's an overload `test(int,string,bool)` then implicit construction will no longer work - this is a general problem with the (Aggregate arg...) feature.That said, there is exactly 1 case where I really, really want some kind of implicit conversion: struct Success {} struct Error { string msg; } alias Result = Algebraic!(Success, Error); Result connectToHost(IPAddress host) { //do some stuff if (operationSucceeded) { return Success(); } else { return Error(statusMessage); } } This currently doesn't work, and you instead have to return Result(Success()) and Result(Error(statusMessage)).This is a great example. If we had ` implicit this(T v)` it could be restricted to accepting literals, struct constructor call expressions and new expressions for `v`. That would allow support for your example, plus allow: void f(Result); f(Success()); f(Error(statusMessage)); This should only work for an exactly matching implicit constructor. Suppose we have a type Q that has an implicit constructor for type P, and P has an implicit constructor for int: void g(Q); g(P()); // OK g(5); // error, int is not implicitly convertible to Q For g(5), the compiler doesn't consider P's ` implicit this(int)`, it only matches an implicit constructor of Q when the argument type is P exactly.
Mar 02 2018
On Monday, 26 February 2018 at 19:25:06 UTC, WebFreak001 wrote:really cool in D. Basically allow modifying implicit construction of a type. Right now we only have struct opAssign/constructor implicit conversion, but this addition would also add it to classes and make it even more convenient. [...] What's your opinion on this? I think it is really useful to have when interoping with scripting languages or doing (de)serialization or for these attribute wrappers which are there because of no function attribute UDAs. I was looking into implementing this to dmd myself but the code on the implicit cast checks looks a bit hardcoded for the types that are in right now and not really suitable to implement this, but maybe someone with more knowledge about dmd internals can do this with ease. This is only a single direction T -> Wrapper and I think this would actually be enough, but it would maybe be possible to add implicit to opCast in the future.Such implicit conversion was once 'on the table', see this thread: https://forum.dlang.org/post/lv9iqs$1jp3$1 digitalmars.com. I also think that controlled implicit conversion would be sometimes useful, notably with wrapper structs. For now, the only solution is to use templated functions (in some cases, all the code using wrapper structs has to be templated...). Pragmatism and cautious design sometimes defeat 'EVIL'.
Feb 27 2018