www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - making a struct an inputRange with free functions

reply Johannes Loher <johannes.loher fg4f.de> writes:
Is there a way to do this? Here is a naive implementation:
https://run.dlang.io/is/JKvL80 .

It does not pass `isInputRange` (I think, because the free functions are
not visible in the scope of `isInputRange`).

Trying to iterate over it with a foreach loop results in a compile error:
Error: invalid foreach aggregate NoRange(0, 0).this(5), define
opApply(), range primitives, or use .tupleof

Thanks for your help!
Apr 16 2018
next sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/16/18 3:10 PM, Johannes Loher wrote:
 Is there a way to do this? Here is a naive implementation:
 https://run.dlang.io/is/JKvL80 .
 
 It does not pass `isInputRange` (I think, because the free functions are
 not visible in the scope of `isInputRange`).
You are correct, it's not possible.
 Trying to iterate over it with a foreach loop results in a compile error:
 Error: invalid foreach aggregate NoRange(0, 0).this(5), define
 opApply(), range primitives, or use .tupleof
The compiler actually looks to see if it has front as a member. If it has that one function, the foreach is attempted. https://run.dlang.io/is/Yzn3ra But this is an implementation detail. It may not be valid in future versions of the compiler. Note, I had to add a ref to your popFront, or it's an infinite loop :) -Steve
Apr 16 2018
prev sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, April 16, 2018 21:10:03 Johannes Loher via Digitalmars-d-learn 
wrote:
 Is there a way to do this? Here is a naive implementation:
 https://run.dlang.io/is/JKvL80 .

 It does not pass `isInputRange` (I think, because the free functions are
 not visible in the scope of `isInputRange`).

 Trying to iterate over it with a foreach loop results in a compile error:
 Error: invalid foreach aggregate NoRange(0, 0).this(5), define
 opApply(), range primitives, or use .tupleof

 Thanks for your help!
It doesn't work unless the module using the range either contains the free functions or imports them. So, I believe that that would mean that for isInputRange to pass, std.range.primitives would have to have access to the functions, which isn't going to happen for anything but dynamic arrays. It's a limitation of UFCS in general (it's similar to why using string lambdas with std.functional has become fairly rare - you can only use functions in them that std.functional already knows about). The only way for the functions to be associated with the type and thus always be available is if they're member functions. So, if you want a type to be a range, you have to declare the range functions as member functions. - Jonathan M Davis
Apr 16 2018
next sibling parent Johannes Loher <johannes.loher fg4f.de> writes:
Am 16.04.2018 um 21:27 schrieb Jonathan M Davis:
 On Monday, April 16, 2018 21:10:03 Johannes Loher via Digitalmars-d-learn 
 wrote:
 Is there a way to do this? Here is a naive implementation:
 https://run.dlang.io/is/JKvL80 .

 It does not pass `isInputRange` (I think, because the free functions are
 not visible in the scope of `isInputRange`).

 Trying to iterate over it with a foreach loop results in a compile error:
 Error: invalid foreach aggregate NoRange(0, 0).this(5), define
 opApply(), range primitives, or use .tupleof

 Thanks for your help!
It doesn't work unless the module using the range either contains the free functions or imports them. So, I believe that that would mean that for isInputRange to pass, std.range.primitives would have to have access to the functions, which isn't going to happen for anything but dynamic arrays. It's a limitation of UFCS in general (it's similar to why using string lambdas with std.functional has become fairly rare - you can only use functions in them that std.functional already knows about). The only way for the functions to be associated with the type and thus always be available is if they're member functions. So, if you want a type to be a range, you have to declare the range functions as member functions. - Jonathan M Davis
Yeah, that's what I guessed, too. Thanks for the clarification.
Apr 16 2018
prev sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Monday, 16 April 2018 at 19:27:28 UTC, Jonathan M Davis wrote:
 [snip]
It really would be nice if it worked with free functions... I was trying to get the example working with Atila's concepts library [1]. I tried re-writing the checkInputRange function so that UFCS is only used when hasMember passes, but that didn't seem to help things. The big issue is when the free functions are in another module that checkInputRange does not know about. The only solution I can think of is a template mixin (below). Then to use, you import InputRange; mixin InputRange; . However, I have no idea how well this will work more generally. mixin template InputRange() { void checkInputRange(R)(inout int = 0) { R r = R.init; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke popFront() auto h = r.front; // can get the front of the range } enum isInputRange(R) = is(typeof(checkInputRange!R)); } [1] https://github.com/atilaneves/concepts
Apr 18 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Wednesday, April 18, 2018 20:39:46 jmh530 via Digitalmars-d-learn wrote:
 On Monday, 16 April 2018 at 19:27:28 UTC, Jonathan M Davis wrote:
 [snip]
It really would be nice if it worked with free functions... I was trying to get the example working with Atila's concepts library [1]. I tried re-writing the checkInputRange function so that UFCS is only used when hasMember passes, but that didn't seem to help things. The big issue is when the free functions are in another module that checkInputRange does not know about. The only solution I can think of is a template mixin (below). Then to use, you import InputRange; mixin InputRange; . However, I have no idea how well this will work more generally. mixin template InputRange() { void checkInputRange(R)(inout int = 0) { R r = R.init; // can define a range object if (r.empty) {} // can test for empty r.popFront; // can invoke popFront() auto h = r.front; // can get the front of the range } enum isInputRange(R) = is(typeof(checkInputRange!R)); } [1] https://github.com/atilaneves/concepts
Occasionally, that aspect of importing and UFCS can be annoying, but on the whole, I don't really see why it matters much, particularly when actually having the free functions available would be an enormous change to how imports work and could cause a ton of other problems. It would mean that code would be affected by which code imported it rather than just the code that it imports, and at that point, you effectively lose control over what's going on. It would mean that just like C/C++ #includes, you couldn't rely on the module being the same every time you imported it. Even if the effect were limited to templated code, it would mean that two supposedly identical instantiations of a template would not necessarily be identical anymore, and they'd have to be recompiled in every module that used them. It would be a disaster in the making. mixins are the closest that we get to that, but in that case, the programmer is specifically stating that they want to reuse that code directly in their own module as if it were declared there rather than using stuff from other modules. Occasionally, that might be limiting, but without those restrictions, you basically don't have a module system anymore. And with mixins, you have control over what affects your module, whereas having the code that imports a module affect it would be more like mixing in code from the outside. And in any case, IMHO, the range API functions aren't really functions that make much sense as free functions anyway. They're not generic, and they're very much tied to the type that they go with - just like opEquals, opAssign, or toString are tied to the type. They're inherently pretty much the opposite of generic. And even if the import rules somehow let you have them as free functions without it causing problems, what would it buy you? The only situation I can think of where it might be useful is if you're dealing with a type that you can't control and thus can't add the member functions to. But in that case, you can always just wrap that type in another type that does declare the range API. So, I don't think that much is lost by not being able to use UFCS to make something a range. - Jonathan M Davis
Apr 18 2018
parent jmh530 <john.michael.hall gmail.com> writes:
On Thursday, 19 April 2018 at 01:33:26 UTC, Jonathan M Davis 
wrote:
 [snip]

 Occasionally, that aspect of importing and UFCS can be 
 annoying, but on the whole, I don't really see why it matters 
 much, particularly when actually having the free functions 
 available would be an enormous change to how imports work and 
 could cause a ton of other problems. It would mean that code 
 would be affected by which code imported it rather than just 
 the code that it imports, and at that point, you effectively 
 lose control over what's going on. It would mean that just like 
 C/C++ #includes, you couldn't rely on the module being the same 
 every time you imported it. Even if the effect were limited to 
 templated code, it would mean that two supposedly identical 
 instantiations of a template would not necessarily be identical 
 anymore, and they'd have to be recompiled in every module that 
 used them. It would be a disaster in the making. mixins are the 
 closest that we get to that, but in that case, the programmer 
 is specifically stating that they want to reuse that code 
 directly in their own module as if it were declared there 
 rather than using stuff from other modules. Occasionally, that 
 might be limiting, but without those restrictions, you 
 basically don't have a module system anymore. And with mixins, 
 you have control over what affects your module, whereas having 
 the code that imports a module affect it would be more like 
 mixing in code from the outside.

 And in any case, IMHO, the range API functions aren't really 
 functions that make much sense as free functions anyway. 
 They're not generic, and they're very much tied to the type 
 that they go with - just like opEquals, opAssign, or toString 
 are tied to the type. They're inherently pretty much the 
 opposite of generic. And even if the import rules somehow let 
 you have them as free functions without it causing problems, 
 what would it buy you? The only situation I can think of where 
 it might be useful is if you're dealing with a type that you 
 can't control and thus can't add the member functions to. But 
 in that case, you can always just wrap that type in another 
 type that does declare the range API. So, I don't think that 
 much is lost by not being able to use UFCS to make something a 
 range.

 - Jonathan M Davis
I get that range functions are very much tied to the type. My default is almost always to include them as member functions, and I don't favor any big breaking changes. With respect to this thread, my thinking had gone to that mention of anemic domain models on the announce board a few days ago [1]. If free functions can't fully replace member functions, then this anemic domain model approach would be limited in D. [1] https://forum.dlang.org/thread/kawrnpsyjugwwtknqauv forum.dlang.org
Apr 19 2018