View mode: basic / threaded / horizontal-split · Log in · Help
June 09, 2012
Why is to(T) pure but roundTo(T) impure?
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
June 09, 2012
Re: Why is to(T) pure but roundTo(T) impure?
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
June 10, 2012
Re: Why is to(T) pure but roundTo(T) impure?
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.
June 10, 2012
Re: Why is to(T) pure but roundTo(T) impure?
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
Top | Discussion index | About this forum | D home