[HN Gopher] The friendship between Haskell and C
       ___________________________________________________________________
        
       The friendship between Haskell and C
        
       Author : signa11
       Score  : 78 points
       Date   : 2023-03-17 10:48 UTC (12 hours ago)
        
 (HTM) web link (typeclasses.substack.com)
 (TXT) w3m dump (typeclasses.substack.com)
        
       | sshine wrote:
       | A recent article posted on HN that didn't get a lot of attention:
       | 
       | Calling Rust functions from Haskell FFI:
       | 
       | https://engineering.iog.io/2023-01-26-hs-bindgen-introductio...
        
       | eggy wrote:
       | Calling Zig from Haskell is a nice variant, and adds something
       | over C.
       | 
       | https://luctielen.com/posts/calling_zig_from_haskell/
        
       | DeathArrow wrote:
       | Calling a C API isn't producing side effects?
        
         | avgcorrection wrote:
         | This question returns no value (does not expect an honest
         | answer) but has the Snark effect on the person being asked.
        
         | Jeff_Brown wrote:
         | xmonad (a tiling window manager) and pandoc (a command-line
         | tool for document conversion) are both written in Haskell.
        
         | chongli wrote:
         | Haskell doesn't have side effects, but it does have effects.
         | This is a subtle distinction that is a bit hard to explain but
         | I'll give it my best shot:
         | 
         | One way to think of Haskell is like a pure functional C
         | preprocessor that also happens to be lazy. When Haskell code is
         | running, pure functional expressions are being evaluated and
         | reduced to their simplest form. This simplest form is not a
         | plain value but actually a program that when run has effects
         | (reading input, printing to the screen) just like a C program.
         | 
         | The last piece of the puzzle is that, unlike the C
         | preprocessor, Haskell doesn't do all of the evaluation ahead of
         | time. The evaluation of these pure functional expressions is
         | interwoven with the execution of the program producing the
         | desired effects. Haskell's type system is used to express the
         | dependencies between inputs and outputs so that the effects are
         | performed in the correct sequence.
        
           | aidenn0 wrote:
           | There was a somewhat tongue-in-cheek article a while back
           | saying "C has no side effects" with the premise being That
           | "when people say C, they really mean CPP, because nobody
           | writes C with the preprocessor disabled" and since CPP is
           | side-effect free, colloquially we can say that C is side-
           | effect free...
        
             | marcosdumay wrote:
             | Hum... You meant to say that the C compiler is pure. And
             | well, yes, it is. Even when you include the compiler
             | itself, instead of only the preprocessor.
             | 
             | You can notice on the GP's description that Haskell will
             | generate those programs at runtime. The Haskell code is
             | running on the same level as the C compiler, but at a very
             | different time.
        
             | ackfoobar wrote:
             | http://conal.net/blog/posts/the-c-language-is-purely-
             | functio...
             | 
             | "The C language is purely functional" and "Haskell is the
             | world's finest imperative programming language" can blow
             | some minds.
        
               | aidenn0 wrote:
               | That's the one.
               | 
               | There's also:
               | 
               | > "Haskell is a dynamically-typed, interpreted language."
               | 
               | From https://aphyr.com/posts/342-typing-the-technical-
               | interview
        
               | [deleted]
        
         | cryptonector wrote:
         | That's allowed in the IO monad, and, really, it's allowed. The
         | idea in Haskell is to have many contexts where side effects are
         | not allowed so that in those contexts your code must be pure,
         | and pure code is easier to write tests for and reason about.
        
         | Laaas wrote:
         | You pretend that they don't for morally pure functions like the
         | ones from math.h.
        
           | ParetoOptimal wrote:
           | And "Fast and loose reasoning is morally correct":
           | 
           | http://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/fa.
           | ..
        
           | eesmith wrote:
           | I remember my surprise on a DEC Alpha in late 2001 when I did
           | something like acos(3) and the floating-point exception
           | triggered a core dump. As I recall, on the other OSes I used,
           | SIGFPE was by default ignored.
        
           | josefx wrote:
           | Isn't everything in math.h spamming errno?
        
             | Joker_vD wrote:
             | It's not an observable side effect because nobody ever
             | observes the value of errno :)
        
               | k0k0r0 wrote:
               | Well, this is not true. However, to be fair, most
               | important use case of errno in my work is debugging in
               | horrible code bases, trying to understand what has
               | happened.
        
             | cryptonector wrote:
             | The FFI wrappers can restore the previous value of errno
             | after reading the newer value after calling whatever
             | foreign function, thus undoing that side-effect, and
             | nothing other than a signal handler could observe this
             | side-effect in the middle of the FFI wrapper's execution.
             | 
             | Oh, note to self: signal handlers need to restore errno
             | values if they call async-signal-safe functions that can
             | set errno.
        
         | WJW wrote:
         | 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 wrote:
           | The 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 wrote:
             | You 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 wrote:
               | "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.
        
               | light_hue_1 wrote:
               | You 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 wrote:
               | What 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.
        
               | jacobmartin wrote:
               | fascinating! 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 wrote:
               | It 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.
        
             | jcranmer wrote:
             | Raising 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 wrote:
               | Yes, 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 wrote:
               | I'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 wrote:
               | There 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.
        
               | nextaccountic wrote:
               | How can replacing x + 0.0 with x not be legal??
        
               | jcranmer wrote:
               | x + 0.0 is not the same as x when x is -0.0: -0.0 + 0.0
               | is +0.0, which is not the same as -0.0, though they do
               | compare equal.
        
               | eesmith wrote:
               | I decided to test every non-NaN 32-bit float, and
               | confirmed that's the only such case.
               | 
               | Edit: Hee-hee! And if I put my source into Godbolt's
               | Compiler Explorer, compile with gcc 12.2 -O3 -ffast-math
               | then my program turns into:                 main:
               | xor     eax, eax               ret
        
               | jcranmer wrote:
               | The other case it doesn't work is that x + 0.0 is defined
               | to quiet an sNaN (because almost every fp operation turns
               | sNaN to qNaN)... except even generally strict floating-
               | point models generally say "fuck you" to sNaN and treat
               | sNaN and qNaN as interchangeable.
               | 
               | (Speaking of fast math, my planned blog post title if I
               | ever write all this up into a blog post is "Floating
               | Point, or How I Learned to Start Worrying and Hate Fast
               | Math.")
        
               | eesmith wrote:
               | I deliberately added a !isnan() to my tests. There be
               | dragons there. ;)
               | 
               | More precisely, my test compared the bit-patterns
               | directly:                 i = 0 .. 2^32-1
               | memcpy(&x, &i, 4);       y = x + 0.0f;       memcpy(&j,
               | &y, 4)       if (i != j) {printf("%f + 0.0 -> %f\n", x,
               | y);
               | 
               | and I'm pretty sure that NaN addition, while resulting in
               | a NaN, does not need to preserve the original bit
               | pattern.
               | 
               | Regarding fast-not-so-math - Praise Kahan!
        
           | mhh__ wrote:
           | errno 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)
        
       ___________________________________________________________________
       (page generated 2023-03-17 23:01 UTC)