digitalmars.D - Possible improvement of phobos map?
- JG (46/46) Jul 04 2022 Hi,
- JG (4/51) Jul 04 2022 I should have added, this is incomplete code one needs to check
- JG (35/97) Jul 04 2022 To be clear I mean something like this:
- Paul Backus (10/27) Jul 04 2022 You can clean these up using the recently-added
- JG (5/37) Jul 05 2022 Thanks that is cleaner. I am not sure if it as clean as if map
- H. S. Teoh (24/37) Jul 05 2022 Yeah, I found this annoying too.
- JG (4/6) Jul 06 2022 Yes, it definitely could (my second version does this). I guess
- H. S. Teoh (7/15) Jul 06 2022 IMO it's worth it. Or maybe use bind internally in Phobos so that we
- Paul Backus (9/25) Jul 06 2022 Disagree. This would be a textbook example of generality creep
- Timon Gehr (8/66) Jul 06 2022 I want this as well, but it is probably better to improve the language
Hi, There was a recent discussion regarding suggesting some special algorithms for associative arrays, which made we wonder if the problem isn't actually that it is a slightly annoying when working with ranges of tuples or structs. For instance you might have code like this ```d auto a = ["a":5, "b":6]; a.byPair.map!(t=>format!"%s=%s"(t[0],t[1])).writeln; ``` or ```d auto r = iota(1,10); auto gcds = cartesianProduct(r,r).map!(t=>gcd(t[0],t[1])); ``` which while not too bad isn't as readable as it could be, and it gets worse when the chain gets longer. With some slight changes to map (which wouldn't be needed if pattern matching exists), one can do this: ```d import std; struct MapPrototype(alias f, R) { R r; bool empty() { return r.empty; } auto front() { return f(r.front.tupleof); } void popFront() { r.popFront; } auto save() { return typeof(this)(r.save); } } auto mapPrototype(alias f, R)(R r) { return MapPrototype!(f,R)(r); } struct Point { int x; int y; } void main() { auto r = iota(1,10); auto gcds = cartesianProduct(r,r).mapPrototype!(gcd); gcds.writeln; auto a = ["a":5, "b":6]; a.byPair.mapPrototype!((name,value)=>format!"%s=%s"(name,value)).writeln; [Point(1,2),Point(3,4)].mapPrototype!((x,y)=>x*x+y*y).writeln; } ``` Thoughts?
Jul 04 2022
On Monday, 4 July 2022 at 19:44:11 UTC, JG wrote:Hi, There was a recent discussion regarding suggesting some special algorithms for associative arrays, which made we wonder if the problem isn't actually that it is a slightly annoying when working with ranges of tuples or structs. For instance you might have code like this ```d auto a = ["a":5, "b":6]; a.byPair.map!(t=>format!"%s=%s"(t[0],t[1])).writeln; ``` or ```d auto r = iota(1,10); auto gcds = cartesianProduct(r,r).map!(t=>gcd(t[0],t[1])); ``` which while not too bad isn't as readable as it could be, and it gets worse when the chain gets longer. With some slight changes to map (which wouldn't be needed if pattern matching exists), one can do this: ```d import std; struct MapPrototype(alias f, R) { R r; bool empty() { return r.empty; } auto front() { return f(r.front.tupleof); } void popFront() { r.popFront; } auto save() { return typeof(this)(r.save); } } auto mapPrototype(alias f, R)(R r) { return MapPrototype!(f,R)(r); } struct Point { int x; int y; } void main() { auto r = iota(1,10); auto gcds = cartesianProduct(r,r).mapPrototype!(gcd); gcds.writeln; auto a = ["a":5, "b":6]; a.byPair.mapPrototype!((name,value)=>format!"%s=%s"(name,value)).writeln; [Point(1,2),Point(3,4)].mapPrototype!((x,y)=>x*x+y*y).writeln; } ``` Thoughts?I should have added, this is incomplete code one needs to check which behavior is wanted decomposition like above or the old behavior.
Jul 04 2022
On Monday, 4 July 2022 at 19:51:01 UTC, JG wrote:On Monday, 4 July 2022 at 19:44:11 UTC, JG wrote:To be clear I mean something like this: ```d import std; struct MapPrototype(alias f, R) { R r; bool empty() { return r.empty; } auto front() { static if(__traits(compiles,f(R.init.front))) { return f(r.front); } else { return f(r.front.tupleof); } } void popFront() { r.popFront; } auto save() { return typeof(this)(r.save); } } auto mapPrototype(alias f, R)(R r) { return MapPrototype!(f,R)(r); } struct Point { int x; int y; } void main() { auto r = iota(1,10); auto gcds = cartesianProduct(r,r).mapPrototype!(gcd); gcds.writeln; auto a = ["a":5, "b":6]; a.byPair.mapPrototype!((name,value)=>format!"%s=%s"(name,value)).writeln; [Point(1,2),Point(3,4)].mapPrototype!((x,y)=>x*x+y*y).writeln; [Point(1,2),Point(3,4)].mapPrototype!(p=>p.x).writeln; } ```Hi, There was a recent discussion regarding suggesting some special algorithms for associative arrays, which made we wonder if the problem isn't actually that it is a slightly annoying when working with ranges of tuples or structs. For instance you might have code like this ```d auto a = ["a":5, "b":6]; a.byPair.map!(t=>format!"%s=%s"(t[0],t[1])).writeln; ``` or ```d auto r = iota(1,10); auto gcds = cartesianProduct(r,r).map!(t=>gcd(t[0],t[1])); ``` which while not too bad isn't as readable as it could be, and it gets worse when the chain gets longer. With some slight changes to map (which wouldn't be needed if pattern matching exists), one can do this: ```d import std; struct MapPrototype(alias f, R) { R r; bool empty() { return r.empty; } auto front() { return f(r.front.tupleof); } void popFront() { r.popFront; } auto save() { return typeof(this)(r.save); } } auto mapPrototype(alias f, R)(R r) { return MapPrototype!(f,R)(r); } struct Point { int x; int y; } void main() { auto r = iota(1,10); auto gcds = cartesianProduct(r,r).mapPrototype!(gcd); gcds.writeln; auto a = ["a":5, "b":6]; a.byPair.mapPrototype!((name,value)=>format!"%s=%s"(name,value)).writeln; [Point(1,2),Point(3,4)].mapPrototype!((x,y)=>x*x+y*y).writeln; } ``` Thoughts?I should have added, this is incomplete code one needs to check which behavior is wanted decomposition like above or the old behavior.
Jul 04 2022
On Monday, 4 July 2022 at 19:44:11 UTC, JG wrote:Hi, There was a recent discussion regarding suggesting some special algorithms for associative arrays, which made we wonder if the problem isn't actually that it is a slightly annoying when working with ranges of tuples or structs. For instance you might have code like this ```d auto a = ["a":5, "b":6]; a.byPair.map!(t=>format!"%s=%s"(t[0],t[1])).writeln; ``` or ```d auto r = iota(1,10); auto gcds = cartesianProduct(r,r).map!(t=>gcd(t[0],t[1])); ``` which while not too bad isn't as readable as it could be, and it gets worse when the chain gets longer.You can clean these up using the recently-added [`std.functional.bind`][1]: ```d a.byPair.map!(bind!((k, v) => format!"%s=%s"(k, v))).writeln; ``` ```d auto gcds = cartesianProduct(r,r).map!(bind!gcd); ``` [1]: https://phobos.dpldocs.info/std.functional.bind.html
Jul 04 2022
On Monday, 4 July 2022 at 19:55:21 UTC, Paul Backus wrote:On Monday, 4 July 2022 at 19:44:11 UTC, JG wrote:Thanks that is cleaner. I am not sure if it as clean as if map implicitly did it, but perhaps it could also be confusing (and plenty of other range based algorithms would need similar changes).Hi, There was a recent discussion regarding suggesting some special algorithms for associative arrays, which made we wonder if the problem isn't actually that it is a slightly annoying when working with ranges of tuples or structs. For instance you might have code like this ```d auto a = ["a":5, "b":6]; a.byPair.map!(t=>format!"%s=%s"(t[0],t[1])).writeln; ``` or ```d auto r = iota(1,10); auto gcds = cartesianProduct(r,r).map!(t=>gcd(t[0],t[1])); ``` which while not too bad isn't as readable as it could be, and it gets worse when the chain gets longer.You can clean these up using the recently-added [`std.functional.bind`][1]: ```d a.byPair.map!(bind!((k, v) => format!"%s=%s"(k, v))).writeln; ``` ```d auto gcds = cartesianProduct(r,r).map!(bind!gcd); ``` [1]: https://phobos.dpldocs.info/std.functional.bind.html
Jul 05 2022
On Mon, Jul 04, 2022 at 07:44:11PM +0000, JG via Digitalmars-d wrote: [...]```d auto a = ["a":5, "b":6]; a.byPair.map!(t=>format!"%s=%s"(t[0],t[1])).writeln; ```Yeah, I found this annoying too. [...]With some slight changes to map (which wouldn't be needed if pattern matching exists), one can do this: ```d[...]a.byPair.mapPrototype!((name,value)=>format!"%s=%s"(name,value)).writeln; [Point(1,2),Point(3,4)].mapPrototype!((x,y)=>x*x+y*y).writeln;[..]``` Thoughts?This is definitely much more pleasant to write. In fact, I wonder if the current map couldn't be extended to handle this. We just need .map to detect if the lambda has multiple arguments, and expand the mapped element accordingly. A lambda argument that takes two parameters cannot be confused with a lambda argument that takes only one, so this wouldn't affect existing code. Something like this: auto map(alias fun, R)(R range) { static if (arity!fun == 1) { ... // current implementation } else { ... // new implementation, expand element with .tupleof // possibly also check if .tupleof.length // matches arity!fun. } } T -- The irony is that Bill Gates claims to be making a stable operating system and Linus Torvalds claims to be trying to take over the world. -- Anonymous
Jul 05 2022
On Wednesday, 6 July 2022 at 01:28:32 UTC, H. S. Teoh wrote:In fact, I wonder if the current map couldn't be extended to handle this.Yes, it definitely could (my second version does this). I guess one downside is that several other range algorithms could do with the same change (rather than using bind which requires no change).
Jul 06 2022
On Wed, Jul 06, 2022 at 09:00:59AM +0000, JG via Digitalmars-d wrote:On Wednesday, 6 July 2022 at 01:28:32 UTC, H. S. Teoh wrote:IMO it's worth it. Or maybe use bind internally in Phobos so that we don't have to repeat the implementation across different Phobos functions. But the user-facing API should be as smooth as possible. T -- People tell me that I'm paranoid, but they're just out to get me.In fact, I wonder if the current map couldn't be extended to handle this.Yes, it definitely could (my second version does this). I guess one downside is that several other range algorithms could do with the same change (rather than using bind which requires no change).
Jul 06 2022
On Wednesday, 6 July 2022 at 17:21:29 UTC, H. S. Teoh wrote:On Wed, Jul 06, 2022 at 09:00:59AM +0000, JG via Digitalmars-d wrote:Disagree. This would be a textbook example of generality creep [1]. map currently (sensibly) requires that the function given to it accept a range element as an argument. Having it go out of its way to try and accept any function that can be cajoled into accepting a range element, if you apply some kind of transformation first (like adding .tupleof), would ultimately make its behavior more complicated and harder to understand. [1] https://forum.dlang.org/thread/q6plhj$1l9$1 digitalmars.comOn Wednesday, 6 July 2022 at 01:28:32 UTC, H. S. Teoh wrote:IMO it's worth it. Or maybe use bind internally in Phobos so that we don't have to repeat the implementation across different Phobos functions. But the user-facing API should be as smooth as possible.In fact, I wonder if the current map couldn't be extended to handle this.Yes, it definitely could (my second version does this). I guess one downside is that several other range algorithms could do with the same change (rather than using bind which requires no change).
Jul 06 2022
On 04.07.22 21:44, JG wrote:Hi, There was a recent discussion regarding suggesting some special algorithms for associative arrays, which made we wonder if the problem isn't actually that it is a slightly annoying when working with ranges of tuples or structs. For instance you might have code like this ```d auto a = ["a":5, "b":6]; a.byPair.map!(t=>format!"%s=%s"(t[0],t[1])).writeln; ``` or ```d auto r = iota(1,10); auto gcds = cartesianProduct(r,r).map!(t=>gcd(t[0],t[1])); ``` which while not too bad isn't as readable as it could be, and it gets worse when the chain gets longer. With some slight changes to map (which wouldn't be needed if pattern matching exists), one can do this: ```d import std; struct MapPrototype(alias f, R) { R r; bool empty() { return r.empty; } auto front() { return f(r.front.tupleof); } void popFront() { r.popFront; } auto save() { return typeof(this)(r.save); } } auto mapPrototype(alias f, R)(R r) { return MapPrototype!(f,R)(r); } struct Point { int x; int y; } void main() { auto r = iota(1,10); auto gcds = cartesianProduct(r,r).mapPrototype!(gcd); gcds.writeln; auto a = ["a":5, "b":6]; a.byPair.mapPrototype!((name,value)=>format!"%s=%s"(name,value)).writeln; [Point(1,2),Point(3,4)].mapPrototype!((x,y)=>x*x+y*y).writeln; } ``` Thoughts?I want this as well, but it is probably better to improve the language than to add additional behavior to Phobos. The fundamental issue is that there is a difference between functions with multiple arguments and functions that accept a single tuple argument. Argument lists are basically tuples, but we need to make them first-class values. Unfortunately, this is not so easy to retrofit into existing language semantics.
Jul 06 2022