6 ms·
Not all C api calls produce (observable) side effects. As a very simple example, the sin() function from math.h is a pure function of type `Double -> Double`.
by WJW 4y ago
Not all C api calls produce (observable) side effects. As a very simple example, the sin() function from math.h is a pure function of type `Double -> Double`. No need to wrap that in IO.
- eesmith 4y agoThe documentation for sin on a Mac says: sin(±infinity) returns a NaN and raises the "invalid" floating-point exception. However, I can't figure out how to make that happen, and my clumsy attempts to figure out <fenv.h> and feraiseexcept() failed to make sin(INFINITY) do something other than return a NaN.
- jacobmartin 4y agoYou should be able to see the floating point exception by testing fetestexcept(FE_INVALID) (if it returns nonzero/true then you have a domain error). errno is also set as a side effect. (edit), for instance this gives me output of "1": #include <math.h> #include <fenv.h> #include <stdio.h> int main(void) { double a = sin(INFINITY); printf("%d\n", fetestexcept(FE_INVALID)); return 0; }
- eesmith 4y ago"1" on my FreeBSD/Intel box. "0" on my Mac/M1 box with the vendor cc but "1" with gcc-12. I think I have an outdated idea that there should be a way to have the exception trigger a SIGFPE, and that modern processors don't do that.
- jacobmartin 4y agofascinating! Does vendor cc link to a different libc? I wonder if that is the problem and the docs are for glibc. And testing just now, I can't even trigger SIGFPE with division by zero (integer or float). I guess all of these exceptions are "caught" now.
- eesmith 4y agoIt took a while but I figured it out. Apple's cc compiles to: a.out: Mach-O 64-bit executable arm64 while my gcc-12 compiles to: a.out: Mach-O 64-bit executable x86_64 which is then emulated via Rosetta.
- light_hue_1 4y agoYou need to enable floating point trapping. It's trivial, but it's not on by default. Although, in that case, this function would still be pure as far as Haskell is concerned. Because its output only depends on its input. Interrupts/exceptions are outside of the scope of the type system as far as an individual function goes.
- jcranmer 4y agoWhat compilers are you using, what flags are you using with those compilers? If you care about it, you need to use the appropriate flags to get a strict floating-point model. If you merely have a precise or fast floating-point model, calling fetestexception is going to result in unspecified behavior. [If you really want to be strict, the result of flags are unspecified if you don't use #pragma FENV_ACCESS ON, but gcc refuses to support FENV_ACCESS pragma, and you instead need to use command-line flags to get the same effect.] > I think I have an outdated idea that there should be a way to have the exception trigger a SIGFPE, and that modern processors don't do that. On Intel, you can clear the exception mask for the INVALID FP exception in mxcsr and get an SIGFPE whenever an operation raises INVALID [assuming, again, strict model to ensure the optimizer respects fp exceptions.] I don't know how, or if, AArch64 can enable hardware traps on floating-point exceptions.
- jcranmer 4y agoRaising a floating-point exception generally means that the exception bit in the floating-point status register is set, and you can test this bit with fetestexcept. Some architectures (but not all) do include some bits in the floating-point control register that will cause a hardware trap to be generated on an exception. There's no standard C function to do this, and even compiler builtin support is kind of sketchy, so your best bet is usually resorting to inline assembly to do this.
- eesmith 4y agoYes, I think I was using outdated/20-year-old experience about how on the DEC Alpha a domain error like this triggered a SIGFPE, and got confused between different ideas of what "exception" means.
- im3w1l 4y agoI'd be concerned about whether the behavior of such a pattern is actually well defined. Like for instance what if the optimizer is super clever and evaluates your sin(infinity) at compile time? Or what if it undoes your bit-set? Or what if it moves the computation before the bit set?
- jcranmer 4y agoThere are essentially three different flavors of floating-point models: strict, precise, and fast. Which one you get is dependent primarily on compiler flags, although most compilers will default to "precise" (a few default to "fast", but they really shouldn't). The strict model requires that everything be evaluated at runtime and that the optimizer be aware of the potential for floating-point operations to have interactions with floating-point control and status words (i.e., rounding modes and fp exceptions)--essentially all optimizations are floating-point are forbidden. The precise model allows compilers to ignore the dependency on rounding mode and fp exceptions, and treat floating-point optimizations as side-effect-free pure expressions, but still requires them to generally follow IEEE 754 arithmetic. As such, standard optimizations like code motion or constant propagation are legal, but arithmetic optimizations (like replacing x + 0.0 with x) are not generally legal. The fast model allows compilers to also apply the arithmetic optimizations. Most people who actually care about this level of detail are aware of the panoply of compiler options that are used to control floating-point optimization in the compiler.
- mhh__ 4y agoerrno can be set upon failure, and also the behaviour is dependant on the global floating point environment (although passing this around with you would be rather cumbersome)
- dllthomas 4y ago> errno can be set upon failure ... can it? I don't see it in the man page. The rest is a good point, either way, though.
- epgui 4y agoThat's probably true, but it's besides the point. Haskell is not about removing side effects in the first place-- it's about making them obvious and contained, and about declaring them. It's about the type system imposing certain constraints on what's allowed and where: none of the above really applies to C.