www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Why is to(T) pure but roundTo(T) impure?

reply "Chris Saunders" <sencha gmail.com> writes:
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
Jun 09 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, June 09, 2012 20:43:42 Chris Saunders wrote:
 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
Jun 09 2012
parent reply "Chris Saunders" <sencha gmail.com> writes:
On Saturday, 9 June 2012 at 19:33:55 UTC, Jonathan M Davis wrote:
 On Saturday, June 09, 2012 20:43:42 Chris Saunders wrote:
 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.
Jun 09 2012
parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, June 10, 2012 04:06:03 Chris Saunders wrote:
 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
Jun 09 2012