digitalmars.D - const assignments problem again
- bearophile (55/55) Aug 06 2011 I have discussed about this topic once in past, but in the meantime I ha...
- Adam D. Ruppe (3/4) Aug 06 2011 That's not good enough.
- bearophile (6/9) Aug 06 2011 I agree, that's why I have added two more points:
- Adam D. Ruppe (15/17) Aug 06 2011 The precedence of the ternary is pretty easy to handle - if there's
- Marco Leise (5/23) Aug 07 2011 The anonymous function solution looks good to me. You have full
- Kagamin (2/3) Aug 07 2011 I suppose accidental overwrite bugs are overrated. I have never seen the...
- Kagamin (1/1) Aug 07 2011 P.S. you would make a better point if it were a const instance of a clas...
- bearophile (34/35) Aug 20 2011 You can't even define V as "const struct" and use it like this:
- Jonathan M Davis (18/26) Aug 07 2011 Being able to use const can be very valuable. For instance, what if you ...
- Kagamin (2/7) Aug 07 2011 I've seen a bug. I fixed two methods: begin and end and sent the patch t...
- Jonathan M Davis (8/22) Aug 07 2011 ??? I don't understand what you're trying to say. I don't know if your E...
- Kagamin (2/3) Aug 07 2011 This is a bad idea too. The code assumes the values are double. If this ...
- Lutger Blijdestijn (15/36) Aug 07 2011 I like the ternary operator the best for this, as it is the simplest. I
- Timon Gehr (2/8) Aug 07 2011 What is the benefit of this, compared to leaving parentheses away entire...
- Lutger Blijdestijn (4/14) Aug 07 2011 Oh, I didn't mean to *always* include parenthesis. Just that when a more...
- Jacob Carlborg (8/16) Aug 07 2011 If D's statements were expressions instead, this could work:
- Don (15/37) Aug 08 2011 I find that much more difficult to read.
- Jacob Carlborg (9/46) Aug 09 2011 I think this syntax fits when using a ternary operator would be too long...
I have discussed about this topic once in past, but in the meantime I have seen this is a quite common problem, so I think it doesn't harm to touch this topic again. This is a direct D translation of the original C or C++ code: double foo; if (abs(e.x - v.x) > double.min) foo = (v.y - e.y) / (v.x - e.x); else foo = double.max; This version is clear, easy to understand, efficient, and it's not bug-prone. Some coding standards require those to use full braces there. But it has some problems too: - It's not DRY, "foo" is repeated three times; - You can't use "auto" there, so if the type of e.x changes, you have to change the foo type manually (double.min is replaceable with typeof(e.x).min). - It's not short code. - And foo can't be const or immutable, I don't like this. In this specific example one of the two branches of the if contains just a constant, so you are allowed to write: double foo = double.max; if (abs(e.x - v.x) > double.min) foo = (v.y - e.y) / (v.x - e.x); But generally you can't do that because the then-else branches of the if are meant to be computed lazily, only one of them. To turn foo constant, you are free to use a temporary mutable variable, but this makes the local namespace even more dirty: double _blue; if (abs(e.x - v.x) > double.min) foo = (v.y - e.y) / (v.x - e.x); else foo = double.max; immutable foo = _blue; In D you are allowed to create a function and call it in place: const foo = { if (abs(e.x - v.x) > double.min) return (v.y - e.y) / (v.x - e.x); else return double.max; }(); - The return type of this delegate is inferenced. This means that both branches of the if must return the same type. Currently in D this disallows some possibilities (I hope this will be fixed), in some cases you have to cast the empty result to the same type of the other if branch; - The code is even longer and I don't like its look a lot (in JavaScript it's fine); - It's not certain that every D compiler will always inline that delegate. This risks lower performance; - That delegate uses values defined outside it, so it can't be pure, so the function that contains such code can't be pure. You solve this problem making the code more complex, passing the needed local variables as arguments to the delegate, but this is not handy. variable, that is constant, but the C conditional expression is bug-prone and it's a bit tricky (it's a common source of various bugs), I don't like it. This code is less maintainable (if you want to add something you sometimes need to convert it again into a normal if). const foo = (abs(e.x - v.x) > double.min) ? ((v.y - e.y) / (v.x - e.x)) : double.max; In my precedent post about this topic I have discussed "nested constness" and another partially silly idea. Recently I have seen a language that suggests me this: const foo; if (abs(e.x - v.x) > double.min) foo = (v.y - e.y) / (v.x - e.x); else foo = double.max; The compiler makes sure all paths assign values with the same type to foo (as in the case of the two returns inside the delegate, that must be of the same assign is far away from the definition the code looks not so nice any more, so this feature is meant for short-range initializations only. Bye, bearophile
Aug 06 2011
Reading this post, I kept thinking "why aren't you just using the ternary operator?"I don't like it.That's not good enough.
Aug 06 2011
Adam D. Ruppe:I agree, that's why I have added two more points: - conditional expression is bug-prone and it's a bit tricky (it's a common source of various bugs), because of it operator precedence too; - It is less maintainable (if you want to add something you sometimes need to convert it again into a normal if). Bye, bearophileI don't like it.That's not good enough.
Aug 06 2011
bearophile:I agree, that's why I have added two more pointsThe precedence of the ternary is pretty easy to handle - if there's more than one thing in there, just use parenthesis. That's the only thing I can think of that would make it any more bug prone than other branches. Perhaps it'd be worth thinking if parens should be required in any non-trivial cases. auto a = something ? "one" : "two"; // ok auto b = a ~ something ? "one" : "two"; // error, please add parens for clarity- It is less maintainableI don't agree that changing to an if is any burden at all. This is like saying "if(x) y;" is unacceptable because you might have to add braces later. Real maintenance headaches are things like duplicated code or unnecessary complexity. This is just a very simple case of syntax translation.
Aug 06 2011
Am 07.08.2011, 06:31 Uhr, schrieb Adam D. Ruppe <destructionator gmail.com>:bearophile:The anonymous function solution looks good to me. You have full flexibility while keeping the variable const and the initialization code is at the declaration site.I agree, that's why I have added two more pointsThe precedence of the ternary is pretty easy to handle - if there's more than one thing in there, just use parenthesis. That's the only thing I can think of that would make it any more bug prone than other branches. Perhaps it'd be worth thinking if parens should be required in any non-trivial cases. auto a = something ? "one" : "two"; // ok auto b = a ~ something ? "one" : "two"; // error, please add parens for clarity- It is less maintainableI don't agree that changing to an if is any burden at all. This is like saying "if(x) y;" is unacceptable because you might have to add braces later. Real maintenance headaches are things like duplicated code or unnecessary complexity. This is just a very simple case of syntax translation.
Aug 07 2011
bearophile Wrote:- And foo can't be const or immutable, I don't like this.I suppose accidental overwrite bugs are overrated. I have never seen them even from evil programmers. If you write random code, overwrite is not your only problem, you can as well read wrong variable or call wrong function. No language will help you if your code is junk. You should fix this in a different way.
Aug 07 2011
P.S. you would make a better point if it were a const instance of a class.
Aug 07 2011
Kagamin:P.S. you would make a better point if it were a const instance of a class.You can't even define V as "const struct" and use it like this: const struct V { double x, y; } // wrong void main() { double x = 1, y = 2; int c = 1; V v; switch (c) { case 1: v = V(x, y); break; case 2: v = V(-x, -y); break; // other cases here default: assert(0); } } This works (the asm shows DMD doesn't inline the function): const struct V { double x, y; } void main() { double x = 1, y = 2; int c = 1; V v = { switch (c) { case 1: return V(x, y); case 2: return V(-x, -y); // other cases here default: assert(0); } }(); } Bye, bearophile
Aug 20 2011
On Sunday 07 August 2011 04:40:52 Kagamin wrote:bearophile Wrote:Being able to use const can be very valuable. For instance, what if you were using std.algorithm.copy and got the arguments backwards? If the source is const, then the compiler will complain, and you'll quickly find the bug. If the source isn't const, then you could accidentally end up copying the target to the source, and it may or may not be an easy bug to catch. I made that exact mistake in C++ with its copy function just the other day. const saved me a lot of headaches. Now, I think that D gives us enough ways to deal with the problem that Bearophile illustrates here (and Bearophile actually showed us a number of ways that D allows us to do what he's trying to do), so I don't think that we really need to do anything to the language to better deal with this situation. But accidental overwrites _can_ be a problem, and that's one of the things that const catches. So, not being able to use const when you should logically be able to due to syntax problems in the language would definitely be a problem - not the biggest problem ever perhaps, but it _would_ be a problem. Fortunately however, D gives us plenty of ways to get around the problem. - Jonathan M Davis- And foo can't be const or immutable, I don't like this.I suppose accidental overwrite bugs are overrated. I have never seen them even from evil programmers. If you write random code, overwrite is not your only problem, you can as well read wrong variable or call wrong function. No language will help you if your code is junk. You should fix this in a different way.
Aug 07 2011
Jonathan M Davis Wrote:Being able to use const can be very valuable. For instance, what if you were using std.algorithm.copy and got the arguments backwards? If the source is const, then the compiler will complain, and you'll quickly find the bug. If the source isn't const, then you could accidentally end up copying the target to the source, and it may or may not be an easy bug to catch.I've seen a bug. I fixed two methods: begin and end and sent the patch to a man. They were calling other methods (sort of begin1 and end1). The man slightly fixed my patch and copied the fixed body of the first method to both of them (they look very similar). The compiler was happy, the application even worked. The bug was catched only because I checked whether the man did it right.
Aug 07 2011
On Sunday 07 August 2011 06:02:36 Kagamin wrote:Jonathan M Davis Wrote:??? I don't understand what you're trying to say. I don't know if your English is just poor or if you're trying to make fun of me. const has value. It obviously doesn't solve everything or catch every bug, but it can help a lot in ensuring that variables that are not supposed to be mutated aren't mutated. You can choose not to use it, and that's fine, but it's part of the language, and many other people value it highly. - Jonathan M DavisBeing able to use const can be very valuable. For instance, what if you were using std.algorithm.copy and got the arguments backwards? If the source is const, then the compiler will complain, and you'll quickly find the bug. If the source isn't const, then you could accidentally end up copying the target to the source, and it may or may not be an easy bug to catch.I've seen a bug. I fixed two methods: begin and end and sent the patch to a man. They were calling other methods (sort of begin1 and end1). The man slightly fixed my patch and copied the fixed body of the first method to both of them (they look very similar). The compiler was happy, the application even worked. The bug was catched only because I checked whether the man did it right.
Aug 07 2011
bearophile Wrote:- You can't use "auto" there, so if the type of e.x changes, you have to change the foo type manually (double.min is replaceable with typeof(e.x).min).This is a bad idea too. The code assumes the values are double. If this assumption is not true, the code is broken.
Aug 07 2011
bearophile wrote: ...In my precedent post about this topic I have discussed "nested constness" and another partially silly idea. Recently I have seen a language that suggests me this: const foo; if (abs(e.x - v.x) > double.min) foo = (v.y - e.y) / (v.x - e.x); else foo = double.max; The compiler makes sure all paths assign values with the same type to foo (as in the case of the two returns inside the delegate, that must be of fragile. If the assign is far away from the definition the code looks not so nice any more, so this feature is meant for short-range initializations only. Bye, bearophileI like the ternary operator the best for this, as it is the simplest. I always write them like this though, liberally include parenthesis and never nest: (condition) ? (truth value) : (false value) When it gets more complicated, you can always rewrite either the whole expression (to if/else or a function) or refactor parts of the expression to small functions. I never find this to be much of a burden. Python has if/else expressions, but due to it's (messy) scoping rules I almost never find them an improvement to if/else statements. I like the single assignment feature, but not for this reason. I think it would be more useful for creating immutable data in general.
Aug 07 2011
Lutger Blijdestin wrote:I like the ternary operator the best for this, as it is the simplest. I always write them like this though, liberally include parenthesis and never nest: (condition) ? (truth value) : (false value)What is the benefit of this, compared to leaving parentheses away entirely?
Aug 07 2011
Timon Gehr wrote:Lutger Blijdestin wrote:Oh, I didn't mean to *always* include parenthesis. Just that when a more complicated expression is involved, I find it often faster to understand if there are some redundant parenthesis.I like the ternary operator the best for this, as it is the simplest. I always write them like this though, liberally include parenthesis and never nest: (condition) ? (truth value) : (false value)What is the benefit of this, compared to leaving parentheses away entirely?
Aug 07 2011
On 2011-08-07 03:19, bearophile wrote:I have discussed about this topic once in past, but in the meantime I have seen this is a quite common problem, so I think it doesn't harm to touch this topic again. This is a direct D translation of the original C or C++ code: double foo; if (abs(e.x - v.x)> double.min) foo = (v.y - e.y) / (v.x - e.x); else foo = double.max;If D's statements were expressions instead, this could work: const foo = if (abs(e.x - v.x)> double.min) (v.y - e.y) / (v.x - e.x); else double.max; -- /Jacob Carlborg
Aug 07 2011
Jacob Carlborg wrote:On 2011-08-07 03:19, bearophile wrote:I find that much more difficult to read. Especially consider const foo = (a > b) ? bar() : baz(); compared to const foo = if (a > b) bar(); else baz(); You have to read quite a lot of code before you get any visual cue that the return value of baz() is used. IMHO: to understand code, I think you really need to know if you're looking at an expression or a statement, so making 'if' do both jobs reduces code clarity. (Would be OK in a language where it was _always_ an expression).I have discussed about this topic once in past, but in the meantime I have seen this is a quite common problem, so I think it doesn't harm to touch this topic again. This is a direct D translation of the original C or C++ code: double foo; if (abs(e.x - v.x)> double.min) foo = (v.y - e.y) / (v.x - e.x); else foo = double.max;If D's statements were expressions instead, this could work: const foo = if (abs(e.x - v.x)> double.min) (v.y - e.y) / (v.x - e.x); else double.max;
Aug 08 2011
On 2011-08-09 07:04, Don wrote:Jacob Carlborg wrote:I think this syntax fits when using a ternary operator would be too long and creating one (or two) new function(s) would just be annoying. But if the if-statement wasn't an expression in all cases it would be very confusing. I see no reason why many of the statements in D couldn't be expressions instead, but I can understand that it's way too late to change that now. -- /Jacob CarlborgOn 2011-08-07 03:19, bearophile wrote:I find that much more difficult to read. Especially consider const foo = (a > b) ? bar() : baz(); compared to const foo = if (a > b) bar(); else baz(); You have to read quite a lot of code before you get any visual cue that the return value of baz() is used. IMHO: to understand code, I think you really need to know if you're looking at an expression or a statement, so making 'if' do both jobs reduces code clarity. (Would be OK in a language where it was _always_ an expression).I have discussed about this topic once in past, but in the meantime I have seen this is a quite common problem, so I think it doesn't harm to touch this topic again. This is a direct D translation of the original C or C++ code: double foo; if (abs(e.x - v.x)> double.min) foo = (v.y - e.y) / (v.x - e.x); else foo = double.max;If D's statements were expressions instead, this could work: const foo = if (abs(e.x - v.x)> double.min) (v.y - e.y) / (v.x - e.x); else double.max;
Aug 09 2011