digitalmars.D.learn - making a struct an inputRange with free functions
- Johannes Loher (8/8) Apr 16 2018 Is there a way to do this? Here is a naive implementation:
- Steven Schveighoffer (9/17) Apr 16 2018 The compiler actually looks to see if it has front as a member.
- Jonathan M Davis (13/21) Apr 16 2018 It doesn't work unless the module using the range either contains the fr...
- Johannes Loher (2/29) Apr 16 2018 Yeah, that's what I guessed, too. Thanks for the clarification.
- jmh530 (21/22) Apr 18 2018 It really would be nice if it worked with free functions...
- Jonathan M Davis (32/54) Apr 18 2018 Occasionally, that aspect of importing and UFCS can be annoying, but on ...
- jmh530 (11/50) Apr 19 2018 I get that range functions are very much tied to the type. My
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
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 .tupleofThe 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
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
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:Yeah, that's what I guessed, too. Thanks for the clarification.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
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
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: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[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
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 DavisI 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