Hi --
I've been trying to learn more about D's purity features after
reading David Nadlinger's interesting post on this topic. While
'purifying' some existing code I discovered that I can't use
roundTo in a pure function, and I don't understand why. Is this a
general problem with most floating-point library calls? (e.g. I
noticed std.math.floor() is impure as well).
"""
import std.conv;
int func(double d) pure { // compiles...
return to!int(d);
}
int func2(double d) pure { //doesn't compile!?!
return roundTo!int(d);
}
"""
Thanks for any pointers!
-Chris

Hi --
I've been trying to learn more about D's purity features after
reading David Nadlinger's interesting post on this topic. While
'purifying' some existing code I discovered that I can't use
roundTo in a pure function, and I don't understand why. Is this a
general problem with most floating-point library calls? (e.g. I
noticed std.math.floor() is impure as well).
"""
import std.conv;
int func(double d) pure { // compiles...
return to!int(d);
}
int func2(double d) pure { //doesn't compile!?!
return roundTo!int(d);
}
"""
Thanks for any pointers!

One of them ends up calling an impure function and the other doesn't. All it
takes is using one low-level function which isn't pure yet, and _boom_, it
can't be pure. This currently happens with pretty much any and all string
conversions, for instance, primarily because the low-level array stuff (like
Appender) can't be pure yet.
In the case of to!int, this overload is called
------------
T toImpl(T, S)(S value)
if (!isImplicitlyConvertible!(S, T) &&
(isNumeric!S || isSomeChar!S) &&
(isNumeric!T || isSomeChar!T))
{
enum sSmallest = mostNegative!S;
enum tSmallest = mostNegative!T;
static if (sSmallest < 0)
{
// possible underflow converting from a signed
static if (tSmallest == 0)
{
immutable good = value >= 0;
}
else
{
static assert(tSmallest < 0);
immutable good = value >= tSmallest;
}
if (!good)
throw new ConvOverflowException("Conversion negative overflow");
}
static if (S.max > T.max)
{
// possible overflow
if (value > T.max)
throw new ConvOverflowException("Conversion positive overflow");
}
return cast(T) value;
}
------------
Notice the lack of functions being called. The only one is
ConvOverflowException's constructor, but apparently that works in a pure
function (even though the constructor isn't marked as pure - maybe it's
because it's an exception which as being thrown).
Whereas, this is roundTo's definition
------------
template roundTo(Target)
{
Target roundTo(Source)(Source value)
{
static assert(isFloatingPoint!Source);
static assert(isIntegral!Target);
return to!Target(trunc(value + (value < 0 ? -0.5L : 0.5L)));
}
}
------------
Note the call to std.math.trunc. It isn't pure, so voila, roundTo can't be
pure. Now, it looks like trunc can't be pure because it's calling a C
function, and there's a good chance that the declaration for that C function
(core.stdc.math.truncl) can be marked as pure, and then trunc could be marked
as pure, and then roundTo could be pure, but that obviously hasn't happened
yet.
In general, it takes very little for a function to be unable to be pure,
especially if it involves low level stuff and/or C stuff in any way.
- Jonathan M Davis

Hi --
I've been trying to learn more about D's purity features after
reading David Nadlinger's interesting post on this topic. While
'purifying' some existing code I discovered that I can't use
roundTo in a pure function, and I don't understand why. Is
this a
general problem with most floating-point library calls? (e.g. I
noticed std.math.floor() is impure as well).
"""
import std.conv;
int func(double d) pure { // compiles...
return to!int(d);
}
int func2(double d) pure { //doesn't compile!?!
return roundTo!int(d);
}
"""
Thanks for any pointers!

One of them ends up calling an impure function and the other
doesn't. All it
takes is using one low-level function which isn't pure yet, and
_boom_, it
can't be pure. This currently happens with pretty much any and
all string
conversions, for instance, primarily because the low-level
array stuff (like
Appender) can't be pure yet.
In the case of to!int, this overload is called
------------
T toImpl(T, S)(S value)
if (!isImplicitlyConvertible!(S, T) &&
(isNumeric!S || isSomeChar!S) &&
(isNumeric!T || isSomeChar!T))
{
enum sSmallest = mostNegative!S;
enum tSmallest = mostNegative!T;
static if (sSmallest < 0)
{
// possible underflow converting from a signed
static if (tSmallest == 0)
{
immutable good = value >= 0;
}
else
{
static assert(tSmallest < 0);
immutable good = value >= tSmallest;
}
if (!good)
throw new ConvOverflowException("Conversion
negative overflow");
}
static if (S.max > T.max)
{
// possible overflow
if (value > T.max)
throw new ConvOverflowException("Conversion
positive overflow");
}
return cast(T) value;
}
------------
Notice the lack of functions being called. The only one is
ConvOverflowException's constructor, but apparently that works
in a pure
function (even though the constructor isn't marked as pure -
maybe it's
because it's an exception which as being thrown).
Whereas, this is roundTo's definition
------------
template roundTo(Target)
{
Target roundTo(Source)(Source value)
{
static assert(isFloatingPoint!Source);
static assert(isIntegral!Target);
return to!Target(trunc(value + (value < 0 ? -0.5L :
0.5L)));
}
}
------------
Note the call to std.math.trunc. It isn't pure, so voila,
roundTo can't be
pure. Now, it looks like trunc can't be pure because it's
calling a C
function, and there's a good chance that the declaration for
that C function
(core.stdc.math.truncl) can be marked as pure, and then trunc
could be marked
as pure, and then roundTo could be pure, but that obviously
hasn't happened
yet.
In general, it takes very little for a function to be unable to
be pure,
especially if it involves low level stuff and/or C stuff in any
way.
- Jonathan M Davis

Thanks Jonathan. Sounds like a practical issue rather than some
theoretical problem -- good to know.

Thanks Jonathan. Sounds like a practical issue rather than some
theoretical problem -- good to know.

The vast majority of purity issues with Phobos are purely an implementation
issue and not any kind of limit in the language. Obviously some stuff can never
be pure (e.g. Clock.currTime or writeln), but for conversions and the like,
it's virtually a guarantee that it's an issue with not all of the lower level
stuff or C stuff being pure like it needs to be. That's slowly getting fixed,
but
we still have quite a ways to go. Probably the biggest problem with that from
the users perspective is format and to!string, because that makes it almost
impossible to make toString pure or to have formatted error messages in
assertions in pure functions. We'll get there though.
- Jonathan M Davis