Bug 8882 – map, filter, iota and zip in pure (and nothrow) functions
Status
RESOLVED
Resolution
FIXED
Severity
normal
Priority
P2
Component
phobos
Product
D
Version
D2
Platform
x86
OS
Windows
Creation time
2012-10-23T16:43:44Z
Last change time
2017-08-30T08:03:11Z
Keywords
rejects-valid
Assigned to
No Owner
Creator
bearophile_hugs
Comments
Comment #0 by bearophile_hugs — 2012-10-23T16:43:44Z
This is not an enhancement request, I think of it as a bug report. This is a spinoff of Issue 8878
To make functional programming in D a better experience, the following functions should be usable in a pure (and possibly nothrow) context:
import std.algorithm: map, filter;
import std.range: iota, zip;
void main() pure nothrow {
auto m = map!q{a * a}([1, 2, 3]);
auto f = filter!q{ a > 1 }([1, 2, 3]);
auto i = iota(1, 10, 2);
auto z = zip([1, 2, 3], [10, 20, 30]);
}
DMD 2.061alpha gives:
test.d(4): Error: pure function 'main' cannot call impure function 'map'
test.d(5): Error: pure function 'main' cannot call impure function 'filter'
test.d(6): Error: pure function 'main' cannot call impure function 'iota'
test.d(7): Error: pure function 'main' cannot call impure function 'zip'
test.d(4): Error: map is not nothrow
test.d(5): Error: filter is not nothrow
test.d(6): Error: iota is not nothrow
test.d(7): Error: zip is not nothrow
test.d(3): Error: function D main 'main' is nothrow yet may throw
Note: zip() accepts a StoppingPolicy, so this can't be nothrow:
import std.range: zip, StoppingPolicy;
void main() pure {
auto z = zip(StoppingPolicy.requireSameLength, [1,2,3], [10,20]);
foreach (t; z) {} // throws
}
Comment #1 by bioinfornatics — 2012-10-24T11:16:25Z
*** Issue 8879 has been marked as a duplicate of this issue. ***
Comment #2 by bearophile_hugs — 2014-01-14T03:16:34Z
The situation has now improved:
import std.algorithm: map, filter;
import std.range: iota, zip;
void main() pure nothrow {
foreach (_; map!q{a * a}([1, 2, 3])) {}
foreach (_; filter!q{ a > 1 }([1, 2, 3])) {}
foreach (_; iota(1, 10, 2)) {}
foreach (_; zip([1, 2, 3], [10, 20, 30])) {}
}
test.d(6): Error: 'std.range.iota!(int, int, int).iota' is not nothrow
test.d(7): Error: 'std.range.Zip!(int[], int[]).Zip.empty' is not nothrow
test.d(7): Error: 'std.range.Zip!(int[], int[]).Zip.popFront' is not nothrow
test.d(3): Error: function 'D main' is nothrow yet may throw
zip can throw because of StoppingPolicy.requireSameLength, and iota(x,y,z) throws when z can be 0. iota(x) and iota(x,y) are nothrow.
I think the problem with zip() can be solved introducing an api change, or adding a new zip overload that takes StoppingPolicy as first template argument.
Fixing iota(x,y,z) is less easy. z is often a value known at compile time, so in theory a new version of iota!z(x, y) can be nothrow at run-time.
Comment #3 by bearophile_hugs — 2014-01-14T13:22:18Z
(In reply to comment #2)
> zip can throw because of StoppingPolicy.requireSameLength, and iota(x,y,z)
> throws when z can be 0. iota(x) and iota(x,y) are nothrow.
even iota(x,y) is not nothrow for floating point arguments:
import std.range: iota;
void main() nothrow {
iota(1.0, 2.0);
}
test.d(3): Error: 'std.range.iota!(double, double).iota' is not nothrow
test.d(2): Error: function 'D main' is nothrow yet may throw
Comment #4 by monarchdodra — 2014-01-16T11:58:06Z
(In reply to comment #2)
> The situation has now improved:
Yeah, thanks Kenji!
> zip can throw because of StoppingPolicy.requireSameLength, and iota(x,y,z)
> throws when z can be 0. iota(x) and iota(x,y) are nothrow.
>
> I think the problem with zip() can be solved introducing an api change, or
> adding a new zip overload that takes StoppingPolicy as first template argument.
Yes, having a run-time condition is awful IMO. It should always be know compile time. It can also create subtle issues for things such as "hasSlicing", which only works for certain stopping tipes.
> Fixing iota(x,y,z) is less easy. z is often a value known at compile time, so
> in theory a new version of iota!z(x, y) can be nothrow at run-time.
>
> even iota(x,y) is not nothrow for floating point arguments:
>
>
> import std.range: iota;
> void main() nothrow {
> iota(1.0, 2.0);
> }
>
> test.d(3): Error: 'std.range.iota!(double, double).iota' is not nothrow
> test.d(2): Error: function 'D main' is nothrow yet may throw
Bah, I think illegal input should Error. Problem solved.
Comment #5 by bearophile_hugs — 2014-01-16T13:50:42Z
(In reply to comment #4)
> Bah, I think illegal input should Error. Problem solved.
Changing an exception -> error changes the API of iota(), but in this case I think there's no need to change the API. "iota(1.0, 2.0);" can be nothrow with small changes in the Phobos code, keeping the same API.
Comment #6 by monarchdodra — 2014-01-16T13:53:55Z
(In reply to comment #5)
> (In reply to comment #4)
>
> > Bah, I think illegal input should Error. Problem solved.
>
> Changing an exception -> error changes the API of iota()
Yes, but I think it's a valid change.
> , but in this case I
> think there's no need to change the API. "iota(1.0, 2.0);" can be nothrow with
> small changes in the Phobos code, keeping the same API.
But that's a special case. What about "iota(1.0, 2.0, 0.1)" ?
Comment #7 by razvan.nitu1305 — 2017-08-28T10:19:44Z
The code in the original bug report now compiles. Closing this as fixed.
Comment #8 by petar.p.kirov — 2017-08-29T10:08:06Z
While, the OP code compiles, zip is not yet nothrow. See:
```
import std.algorithm: map, filter;
import std.range: iota, zip, array;
import std.typecons : tuple;
auto get() pure nothrow
{
auto m = map!q{a * a}([1, 2, 3]);
auto f = filter!q{ a > 1 }([1, 2, 3]);
auto i = iota(1, 10, 2);
auto z = zip([1, 2, 3], [10, 20, 30]);
return tuple(m.array, f.array, i.array, z.array);
}
void main()
{
import std.stdio;
writeln(get());
}
```
test.d(11): Error: function std.array.array!(Zip!(int[], int[])).array is not nothrow
test.d(4): Error: nothrow function test.get may throw
Comment #9 by razvan.nitu1305 — 2017-08-30T07:53:43Z
(In reply to ZombineDev from comment #8)
> While, the OP code compiles, zip is not yet nothrow. See:
>
> ```
> import std.algorithm: map, filter;
> import std.range: iota, zip, array;
> import std.typecons : tuple;
>
> auto get() pure nothrow
> {
> auto m = map!q{a * a}([1, 2, 3]);
> auto f = filter!q{ a > 1 }([1, 2, 3]);
> auto i = iota(1, 10, 2);
> auto z = zip([1, 2, 3], [10, 20, 30]);
> return tuple(m.array, f.array, i.array, z.array);
> }
>
> void main()
> {
> import std.stdio;
> writeln(get());
> }
> ```
>
> test.d(11): Error: function std.array.array!(Zip!(int[], int[])).array is
> not nothrow
> test.d(4): Error: nothrow function test.get may throw
There already is issue 11466 [1] which addresses problem, so I suggest we close this and fix that one.
[1] https://issues.dlang.org/show_bug.cgi?id=11466
Comment #10 by petar.p.kirov — 2017-08-30T08:03:11Z
(In reply to RazvanN from comment #9)
> (In reply to ZombineDev from comment #8)
> > While, the OP code compiles, zip is not yet nothrow. See:
> >
> > ```
> > import std.algorithm: map, filter;
> > import std.range: iota, zip, array;
> > import std.typecons : tuple;
> >
> > auto get() pure nothrow
> > {
> > auto m = map!q{a * a}([1, 2, 3]);
> > auto f = filter!q{ a > 1 }([1, 2, 3]);
> > auto i = iota(1, 10, 2);
> > auto z = zip([1, 2, 3], [10, 20, 30]);
> > return tuple(m.array, f.array, i.array, z.array);
> > }
> >
> > void main()
> > {
> > import std.stdio;
> > writeln(get());
> > }
> > ```
> >
> > test.d(11): Error: function std.array.array!(Zip!(int[], int[])).array is
> > not nothrow
> > test.d(4): Error: nothrow function test.get may throw
>
> There already is issue 11466 [1] which addresses problem, so I suggest we
> close this and fix that one.
>
> [1] https://issues.dlang.org/show_bug.cgi?id=11466
I missed that one, so yes, we should close this one, thanks.