www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Ultra-pure map()?

reply David Held <dmd wyntrmute.com> writes:
import std.algorithm;
import std.stdio;
import std.conv;

class Trivial
{
     int sideEffect() { return n++; }
     override string toString() pure { return to!string(n); }
     int n;
}

void main()
{
     Trivial[] objs = [ new Trivial ];
     map!(o => o.sideEffect())(objs);
     writeln(objs);	// [0]
     foreach (o; objs) o.sideEffect();
     writeln(objs);	// [1]
}

Can someone explain to me why map() is not equivalent to foreach in the 
code above?  From what I can tell, map() doesn't do anything at all on 
objs, even though it is a perfectly legitimate range (as far as I can tell).

Dave
Dec 27 2013
next sibling parent reply "David Nadlinger" <code klickverbot.at> writes:
On Saturday, 28 December 2013 at 01:41:35 UTC, David Held wrote:
 Can someone explain to me why map() is not equivalent to 
 foreach in the code above?  From what I can tell, map() doesn't 
 do anything at all on objs, even though it is a perfectly 
 legitimate range (as far as I can tell).
map() constructs a range that invokes a given function on the source range if an element is requested – but only then. In other words, map is fully lazy. David
Dec 27 2013
parent David Held <dmd wyntrmute.com> writes:
On 12/27/2013 5:46 PM, David Nadlinger wrote:
 On Saturday, 28 December 2013 at 01:41:35 UTC, David Held wrote:
 Can someone explain to me why map() is not equivalent to foreach in
 the code above?  From what I can tell, map() doesn't do anything at
 all on objs, even though it is a perfectly legitimate range (as far as
 I can tell).
map() constructs a range that invokes a given function on the source range if an element is requested – but only then. In other words, map is fully lazy.
Functional programming was surely invented by labor unions! Dave
Dec 27 2013
prev sibling parent reply "John Colvin" <john.loughran.colvin gmail.com> writes:
On Saturday, 28 December 2013 at 01:41:35 UTC, David Held wrote:
 import std.algorithm;
 import std.stdio;
 import std.conv;

 class Trivial
 {
     int sideEffect() { return n++; }
     override string toString() pure { return to!string(n); }
     int n;
 }

 void main()
 {
     Trivial[] objs = [ new Trivial ];
     map!(o => o.sideEffect())(objs);
     writeln(objs);	// [0]
     foreach (o; objs) o.sideEffect();
     writeln(objs);	// [1]
 }

 Can someone explain to me why map() is not equivalent to 
 foreach in the code above?  From what I can tell, map() doesn't 
 do anything at all on objs, even though it is a perfectly 
 legitimate range (as far as I can tell).

 Dave
Map is lazy and is never iterated over in your code, therefore no side effects.
Dec 27 2013
parent reply Marco Leise <Marco.Leise gmx.de> writes:
Am Sat, 28 Dec 2013 01:54:26 +0000
schrieb "John Colvin" <john.loughran.colvin gmail.com>:

 On Saturday, 28 December 2013 at 01:41:35 UTC, David Held wrote:
 import std.algorithm;
 import std.stdio;
 import std.conv;

 class Trivial
 {
     int sideEffect() { return n++; }
     override string toString() pure { return to!string(n); }
     int n;
 }

 void main()
 {
     Trivial[] objs = [ new Trivial ];
     map!(o => o.sideEffect())(objs);
     writeln(objs);	// [0]
     foreach (o; objs) o.sideEffect();
     writeln(objs);	// [1]
 }

 Can someone explain to me why map() is not equivalent to 
 foreach in the code above?  From what I can tell, map() doesn't 
 do anything at all on objs, even though it is a perfectly 
 legitimate range (as far as I can tell).

 Dave
Map is lazy and is never iterated over in your code, therefore no side effects.
Yeah, this is kind of unintended usage. Typically with map you take some input range, apply some algorithm to each element, and return a range of the results. Side effects and altering the input object itself makes me want to pull out my crucifix. You shall not have impurity in your functional style code! -- Marco
Dec 27 2013
parent reply David Held <dmd wyntrmute.com> writes:
On 12/27/2013 7:32 PM, Marco Leise wrote:> [...]
 Side effects and altering the input object itself makes me
 want to pull out my crucifix. You shall not have impurity in
 your functional style code!
Why not? There are many impure functional languages, and most non-functional languages that allow functional style allow mutation. OOP is all about hiding state, which is the opposite of referential transparency. Are you saying we should never map/fold over OOP ranges? That seems like an unnecessary restriction for dogma's sake. Obviously, map() has to be lazy to support infinite ranges. But I assume that reduce() must be eager so that you actually get a result (I mean, it could probably be made lazy at enormous expense, but that would just be silly). So, if you want side effects, I guess you have to do the slightly dirty trick of calling reduce() without actually reducing anything. I guess the "right" thing to do would be to make a new algorithm that implements an eager map() but perhaps doesn't bother with the result, called "invoke()". This carries none of the semantic baggage of well-known pure higher-order functions, and even sounds more OOP-like. Most of the other features of map() (like parallel iteration) are pretty nice to have in eager form. Dave
Dec 28 2013
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
David Held:

 Why not?
Because mixing map/filter and wild unrestrained side effects is asking for troubles (bugs in your code). Bye, bearophile
Dec 28 2013
prev sibling next sibling parent reply "FreeSlave" <freeslave93 gmail.com> writes:
On Saturday, 28 December 2013 at 09:18:00 UTC, David Held wrote:
 On 12/27/2013 7:32 PM, Marco Leise wrote:> [...]
 Side effects and altering the input object itself makes me
 want to pull out my crucifix. You shall not have impurity in
 your functional style code!
Why not? There are many impure functional languages, and most non-functional languages that allow functional style allow mutation. OOP is all about hiding state, which is the opposite of referential transparency. Are you saying we should never map/fold over OOP ranges? That seems like an unnecessary restriction for dogma's sake. Obviously, map() has to be lazy to support infinite ranges. But I assume that reduce() must be eager so that you actually get a result (I mean, it could probably be made lazy at enormous expense, but that would just be silly). So, if you want side effects, I guess you have to do the slightly dirty trick of calling reduce() without actually reducing anything. I guess the "right" thing to do would be to make a new algorithm that implements an eager map() but perhaps doesn't bother with the result, called "invoke()". This carries none of the semantic baggage of well-known pure higher-order functions, and even sounds more OOP-like. Most of the other features of map() (like parallel iteration) are pretty nice to have in eager form. Dave
If you want to get result just now, then use 'array' function from std.array module. map!fun(range).array; or array(map!fun(range));
Dec 28 2013
parent David Held <dmd wyntrmute.com> writes:
On 12/28/2013 2:07 AM, FreeSlave wrote:
 [...]
 If you want to get result just now, then use 'array' function from
 std.array module.

 map!fun(range).array;

 or

 array(map!fun(range));
Syntactically compact and slightly better expression of intent, but much less efficient than just calling reduce(). Dave
Dec 29 2013
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/28/2013 10:17 AM, David Held wrote:
 On 12/27/2013 7:32 PM, Marco Leise wrote:> [...]
  > Side effects and altering the input object itself makes me
  > want to pull out my crucifix. You shall not have impurity in
  > your functional style code!

 Why not?  There are many impure functional languages, and most
 non-functional languages that allow functional style allow mutation. OOP
 is all about hiding state, which is the opposite of referential
 transparency.
That's news to me. OOP does not mandate a procedural programming style.
 ...
 Obviously, map() has to be lazy to support infinite ranges.  ...
 So, if you want side effects, I guess you have to do
 the slightly dirty trick of calling reduce() without actually reducing
 anything.
 ...
What's the point? There are cleaner ways of doing the same. The implementation of map assumes that the result is independent of how it is iterated, and using it with callables that make it fail this criterion is usually at best confusing and at worst a bug.
 I guess the "right" thing to do would be to make a new algorithm that
 implements an eager map() but perhaps doesn't bother with the result,
 called "invoke()".  ...
I wouldn't call this an 'eager map'. It's a shallow wrapper around a foreach loop.
Dec 28 2013
parent reply David Held <dmd wyntrmute.com> writes:
On 12/28/2013 5:13 AM, Timon Gehr wrote:
 [...]
 I wouldn't call this an 'eager map'. It's a shallow wrapper around a
 foreach loop.
The point being that foreach loops aren't composable. Dave
Dec 29 2013
next sibling parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 12/29/2013 02:11 PM, David Held wrote:> On 12/28/2013 5:13 AM, Timon 
Gehr wrote:
 [...]
 I wouldn't call this an 'eager map'. It's a shallow wrapper around a
 foreach loop.
The point being that foreach loops aren't composable.
Agreed. However, if they were composable they would have to be at the end of the chain because when they don't produce a range, no other function can be added after them. If we imagine a foreach that produces a range, then it becomes map.
 Dave
Ali
Dec 29 2013
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 12/29/2013 11:11 PM, David Held wrote:
 On 12/28/2013 5:13 AM, Timon Gehr wrote:
 [...]
 I wouldn't call this an 'eager map'. It's a shallow wrapper around a
 foreach loop.
The point being that foreach loops aren't composable. Dave
Wrapping it will not change that.
Dec 29 2013