www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - std.range.put vs R.put: Best practices?

reply Jon Degenhardt <jond noreply.com> writes:
Documentation for std.range.put 
(https://dlang.org/phobos/std_range_primitives.html#.put) has the 
intriguing line:

 put should not be used "UFCS-style", e.g. r.put(e). Doing this 
 may call R.put directly, by-passing any transformation feature 
 provided by Range.put. put(r, e) is prefered.
This raises the question of whether std.range.put is always preferred over calling an output range's 'put' method, or if there are times when calling an output range's 'put' method directly is preferred. Also, it seems an easy oversight to unintentionally call the wrong one. Does anyone have recommendations or best practice suggestions for which form to use and when? --Jon
Aug 20 2017
parent reply Mike Parker <aldacron gmail.com> writes:
On Sunday, 20 August 2017 at 18:08:27 UTC, Jon Degenhardt wrote:
 Documentation for std.range.put 
 (https://dlang.org/phobos/std_range_primitives.html#.put) has 
 the intriguing line:

 put should not be used "UFCS-style", e.g. r.put(e). Doing this 
 may call R.put directly, by-passing any transformation feature 
 provided by Range.put. put(r, e) is prefered.
This raises the question of whether std.range.put is always preferred over calling an output range's 'put' method, or if there are times when calling an output range's 'put' method directly is preferred. Also, it seems an easy oversight to unintentionally call the wrong one. Does anyone have recommendations or best practice suggestions for which form to use and when? --Jon
It's recommended to always use the utility function in std.range unless you are working with an output range that has a well known put implementation. The issue is that put can be implemented to take any number or type of arguments, but as long as it has an implementation with one parameter of the range's element type, then the utility function will do the right thing internally whether you pass multiple elements, a single element, an array... It's particularly useful in generic code where most ranges are used. But again, if you are working with a specific range type then you can do as you like. Also, when the output range is a dynamic array, UFCS with the utility function is fine. As for mitigating the risk of calling the wrong one, when you do so you'll either get a compile-time error because of a parameter mismatch or it will do the right thing. If there's another likely outcome, I'm unaware of it.
Aug 20 2017
parent reply Jonathan M Davis via Digitalmars-d-learn writes:
On Monday, August 21, 2017 02:34:23 Mike Parker via Digitalmars-d-learn 
wrote:
 On Sunday, 20 August 2017 at 18:08:27 UTC, Jon Degenhardt wrote:
 Documentation for std.range.put
 (https://dlang.org/phobos/std_range_primitives.html#.put) has

 the intriguing line:
 put should not be used "UFCS-style", e.g. r.put(e). Doing this
 may call R.put directly, by-passing any transformation feature
 provided by Range.put. put(r, e) is prefered.
This raises the question of whether std.range.put is always preferred over calling an output range's 'put' method, or if there are times when calling an output range's 'put' method directly is preferred. Also, it seems an easy oversight to unintentionally call the wrong one. Does anyone have recommendations or best practice suggestions for which form to use and when? --Jon
It's recommended to always use the utility function in std.range unless you are working with an output range that has a well known put implementation. The issue is that put can be implemented to take any number or type of arguments, but as long as it has an implementation with one parameter of the range's element type, then the utility function will do the right thing internally whether you pass multiple elements, a single element, an array... It's particularly useful in generic code where most ranges are used. But again, if you are working with a specific range type then you can do as you like. Also, when the output range is a dynamic array, UFCS with the utility function is fine. As for mitigating the risk of calling the wrong one, when you do so you'll either get a compile-time error because of a parameter mismatch or it will do the right thing. If there's another likely outcome, I'm unaware of it.
To add to that, the free function put handles putting different character types to a range of characters (IIRC, it also handles putting entire strings as well), whereas a particular implementation of put probably doesn't. In principle, a specific range type could do everything that the free function does, but it's highly unlikely that it will. In general, it's really just better to use the free function put, and arguably, we should have used a different function name for the output ranges themselves with the idea that the free function would always be the one called, and it would call the special function that the output ranges defined. Unfortunately, however, that's not how it works. In general, IMHO, output ranges really weren't thought out well enough. It's more like they were added as a countepart to input ranges because Andrei felt like they needed to be there rather than having them be fully fleshed out on their own. The result is a basic idea that's very powerful but that suffers in the details and probably needs at least a minor redesign (e.g. the output API has no concept of an output range that's full). In any case, I'd just suggest that you never use put with UFCS. Unfortunately, if you're using UFCS enough, it becomes habit to just call the function as if it were a member function, which is then a problem when using output ranges, but we're kind of stuck at this point. On the bright side, it's really only likely to cause issues in generic code where the member function might work with your tests but not everything that's passed to it. In other cases, if what you're doing doesn't work with the member function, then the code won't compile, and you'll know to switch to using the free function. - Jonathan M Davis
Aug 20 2017
parent Jon Degenhardt <jond noreply.com> writes:
On Monday, 21 August 2017 at 05:58:01 UTC, Jonathan M Davis wrote:
 On Monday, August 21, 2017 02:34:23 Mike Parker via 
 Digitalmars-d-learn wrote:
 On Sunday, 20 August 2017 at 18:08:27 UTC, Jon Degenhardt 
 wrote:
 Documentation for std.range.put
 (https://dlang.org/phobos/std_range_primitives.html#.put) has

 the intriguing line:
 put should not be used "UFCS-style", e.g. r.put(e). Doing 
 this
 may call R.put directly, by-passing any transformation 
 feature
 provided by Range.put. put(r, e) is prefered.
This raises the question of whether std.range.put is always preferred over calling an output range's 'put' method, or if there are times when calling an output range's 'put' method directly is preferred. Also, it seems an easy oversight to unintentionally call the wrong one. Does anyone have recommendations or best practice suggestions for which form to use and when?
It's recommended to always use the utility function in std.range unless you are working with an output range that has a well known put implementation. The issue is that put can be implemented to take any number or type of arguments, but as long as it has an implementation with one parameter of the range's element type, then the utility function will do the right thing internally whether you pass multiple elements, a single element, an array... It's particularly useful in generic code where most ranges are used. But again, if you are working with a specific range type then you can do as you like. Also, when the output range is a dynamic array, UFCS with the utility function is fine. As for mitigating the risk of calling the wrong one, when you do so you'll either get a compile-time error because of a parameter mismatch or it will do the right thing. If there's another likely outcome, I'm unaware of it.
To add to that, the free function put handles putting different character types to a range of characters (IIRC, it also handles putting entire strings as well), whereas a particular implementation of put probably doesn't. In principle, a specific range type could do everything that the free function does, but it's highly unlikely that it will. In general, it's really just better to use the free function put, and arguably, we should have used a different function name for the output ranges themselves with the idea that the free function would always be the one called, and it would call the special function that the output ranges defined. Unfortunately, however, that's not how it works. In general, IMHO, output ranges really weren't thought out well enough. It's more like they were added as a countepart to input ranges because Andrei felt like they needed to be there rather than having them be fully fleshed out on their own. The result is a basic idea that's very powerful but that suffers in the details and probably needs at least a minor redesign (e.g. the output API has no concept of an output range that's full). In any case, I'd just suggest that you never use put with UFCS. Unfortunately, if you're using UFCS enough, it becomes habit to just call the function as if it were a member function, which is then a problem when using output ranges, but we're kind of stuck at this point. On the bright side, it's really only likely to cause issues in generic code where the member function might work with your tests but not everything that's passed to it. In other cases, if what you're doing doesn't work with the member function, then the code won't compile, and you'll know to switch to using the free function.
Mike, Jonathan - Thanks for the detailed responses! Yes, by habit I use UFCS, there is where potential for the wrong call comes from. I agree also that output ranges are very powerful in concept, but the details are not fully fleshed out at this point. A few enhancements could make it much more compelling. --Jon
Aug 21 2017