[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)