Hacker News new | past | comments | ask | show | jobs | submit | more sjolsen's comments login

>Given the world we live in, right now, why does mathematics continue to insist on minified expressions?

Because it is concise, precise, and widely accepted.

>Given the option, most development teams would choose to read and write against verbose source code, rather than scrape obfuscated variables and method signatures out of a minified, transpiled, compressed package.

What's your point? Source code is not the same thing as mathematics, not for the majority of software and not for the majority of mathematics as practiced by mathematicians. Verbosity makes sense when your domain involves concrete entities like "customers" and "widgets" and "thermal sensors." When your domain involves abstract entities like "ring homomorphisms" and "clopen sets" and "vector spaces," it doesn't. If you're writing a database, a name like "transaction_mutex" is more descriptive than "m." If you're universally quantifying over the domain of an arbitrary continuous function on the reals, "x" is about as descriptive -- and as conventional -- as any name you can come up with.

As an aside, I really wish we would dispense with the non-word "transpile." We have a word for translating a program from one language to another: compile.

>So why do we continue this archaic practice of obscure, inscrutable symbols in mathematics?

Because it works very well. I'm not sure what else to say. You spend maybe fifteen minutes learning about, say, the symbol "∂" when you're introduced to multivariate calculus, and then for the rest of time you have an extremely concise way of expressing a variety of combinations of partial derivatives that can be understood by anyone who has also been introduced to multivariate calculus.

Don't get me wrong, there are actual problems with mathematical notation -- overloading, "abuse of notation," and as often as not just plain omitting information -- but the use of non-ASCII symbols and short variable names are not among them.


Strictly speaking, it's possible for different sorting algorithms to produce different results if you sort using a weak order rather than a total order.


Or an unstable sort (which none of grandparent's examples are, but aren't uncommon in naive implementations)


>And people wonder why C++ programs compile so slowly.

People have been aware of this problem for some time. It's one of the reasons the C++ community is trying to develop a proper module system (or was the last time I checked, which was a while back).


By the time C++ has a working module system it will be as relevant as Fortran is now.


There's an experimental module system already in Visual C++, and I think in clang as well. It will be in the 2019 standard at the very latest.

I like Rust and all, but C++ isn't going away in the next three years.


I don't think the complaint is that the compiler won't accept a struct sockaddr_in* where the API expects a struct sockaddr* . I think the complaint is that the API is designed in such a way that obtaining the required struct sockaddr* involves casting between incompatible types. There are more robust ways of doing type erasure, even in C.


>one of those things that divide programmers into two groups: one that instantly sees the elegance of this design and thinks it's so natural and obvious, and one that gets eternally confused by it.

Believe it or not, there are people who are capable of figuring out C's cute little "round trip through the integers" trick, but who would rather not deal with that bullshit every time they want to compare two values of non-numerical type. It's not elegant, it's not natural, it's only "obvious" to those of us who have had the standard workarounds for C's inexpressiveness drilled into our skulls, and if it were any different, we wouldn't bother with comparison operators in the first place!

I mean, I get that it's fun to squeeze useful semantics out of terse code, especially in an environment as constrained as C. But please don't act like the rest of us are stupid just because what you regard as elegant, we regard as more of a clever hack to be put up with for want of a simple equality operator.


compare two values of non-numerical type

everything is "numerical" or can be interpreted as such. It's not C-specific at all, it's just a fact of how computers fundamentally operate. I suspect that might be why this issue is so divisive: if you started low-level with hardware, Asm and the like, then C does feel natural and obvious. If you started high-level with some other HLL, one that abstracts and hides this "essence", it won't.

Pointers are another example of something that is obvious to some but a huge mass of confusion and frustration to others. I've taught programming (mostly Asm and C), and this is quite common. You're not stupid, but you just aren't in the right perspective to understand it.

Those who are strong advocates of functional programming and wonder why others don't see the elegance probably think the same way about me. ;-)


>Pointers are another example of something that is obvious to some but a huge mass of confusion and frustration to others. I've taught programming (mostly Asm and C), and this is quite common. You're not stupid, but you just aren't in the right perspective to understand it.

I understand pointers. I understand integer-based encoding. I'm a firmware programmer, I started with FORTRAN and C-with-classes-style C++, I get how computers work. And do you know what makes computers work? Abstractions. Integers are just an abstraction on top of bit vectors are just an abstraction on top of memory are just an abstraction on top of flip-flops, on top of gates, transistors, digital circuitry, analog circuitry, the laws of electromagnetism -- the only reason we are able to construct something as complex as a pocket calculator and have it work at all is clear, mathematical abstraction. The fact that, in 2016, using such a fundamental abstraction as equality of non-numerical values requires anything beyond the two characters "==" is patently absurd.

My "frustration" is not a result of having trouble grasping first-year C.S. concepts. Nor is it the result of the mild incovenience of specifically having to use "strcmp" instead of "==". It's the result of working in an industry where the value of anything beyond first-year C.S. concepts is completely unrecognized, because it's not "how computers fundamentally operate." Both low-level and high-level computational abstractions are useful, and trying to accomplish anything interesting in a system which completely eschews one class of abstraction in favor of the other, whether it's C or Haskell, is hell.

But hey, it beats the hell out of Javascript :)


I don't think he was calling you stupid, podner :)

I just think "ah, it's a yes we have no bananas" and stop thinking about it :) It's nonzero cognitive load but it's not much.

I've dealt with enough active-low/inverting hardware such that "on" = 0 that it actually doesn't bother me at all. Assembly has that all through it at times.

Compared to reading code where people treat cell [1] as the first significant cell of an array ( usually translated from FORTRAN ) , this is a picnic.


>Bit twiddling, ANDing, and ORing are specific and absolute

So what? You could implement addition using bitwise operators, but it makes for much more readable and less error-prone code to abstract it into an addition operator. Code should clearly express intent, and if your intent is to set, say, bits 5 though 9 of some register to some value, the clearest expression of that is something like (given a right-open range convention, which is well-established in this particular language):

    register.set_bits(5..10, value);
If I look at the above statement, I know what the author of the code intended it to do and can rely on (or manually verify once) the correctness of the implementation of "set_bits." If instead I'm reading code like

    register = (register & 0xFC1F) | (value << 5);
I have to work out in my head what the bit fiddling is doing, and from that guess what the author meant the code to do. I can't know whether he or she made a mistake in translating the intent to bitwise operations (say I know from context that value is less than 8; did they really mean to clear bits 8 and 9?), because the intent isn't expressed anywhere.

>For example, what happens when I specify a range of 0..100 for a 32 bit int?

What happens is that you've violated a precondition of the API. Because this is Rust, I would expect the operation to be implemented in such a way as to panic (in debug mode), or just fail to compile, since selecting a bit range like this at run time is extremely uncommon.

>what if the value is too big for the range?

Same thing as if you weren't using the abstraction around the bitwise operators: either you'd clobber higher bits, or the implementation would truncate the value.

Again, though, I would expect a debug-mode panic.

>APIs around small operations like this introduce ambiguity that wasn't present before.

I'm not sure what "ambiguity" you see here. There's the issue of whether the range is right-open or -closed, but again right-open ranges are well-established in Rust.


>It seems keywords are added to ensure serialization/atomicity, then compilers find a way to optimize it away/make it useless

No, what happens is keywords (or more broadly, semantics) are added which provide certain guarantees given certain preconditions. Then, developers write code that depends on those guarantees and fails to meet the preconditions, but happens to work anyway. At some point, the implementation adds some optimization which -- while still providing the appropriate guarantees when the preconditions are met -- breaks that code.

I know C and C++ can be a royal pain in the ass sometimes, but I have little sympathy for developers who flip the "I know what I'm doing, please assume my code is right and make it run fast" switch, then get upset with the compiler when it turns out that their code actually _isn't_ right and the compiler performs an optimization they weren't expecting. If you don't want the compiler to reorder your memory access, then don't fuck around with the memory_order flags.


On that particular point of memory orders, this is true.

But on other points about how compiler optimize the simple alternative (-O0) is the binary to be painfully slow, while you still have a dozen of flag to make the binary both fast and safe, but the safe flags are not activated by default. The unsafe mode is activated by default when you compile with -O2. How is this useful? It is just crazy.


> Then, developers write code that depends on those guarantees and fails to meet the preconditions, but happens to work anyway.

This reminds me of the memcpy/memmove issue. You should use "move" when areas overlap, not memcpy

The real question is: why are there two versions? Checking if the memory overlap is very cheap compared to copying, let's say 16 elements. And if you're memcpy'ing smaller sizes you can probably do it manually

> I know C and C++ can be a royal pain in the ass sometime

Yes, they lack enough information to know what you are actually trying to do and have to guess a lot of things. Not sure how "C++ smart" are modern compilers. (Example: you pass an object by value, and only read one field, can it optimize this?)


> Example: you pass an object by value, and only read one field, can it optimize this?

Yes.

    struct S { int a,b; };
    void foo(int); // Some external function
    void bar(S s) { foo(s.a); }
    void baz() { bar(S{42, 55}); }
compiles the baz function to (-O2 on GCC 4.9)

    movl    $42, %edi
    jmp     _Z3fooi


Copying short non-overlapping chunks of memory is common in a lot of workloads (think manipulation of short strings) and it does happen in tight loops where an overlap check with two additions, two comparisons and two branches is a comparable amount of work to what memcpy() does.

Why should I have to manually implement memcpy() for small sizes when I know the memory won't overlap?


I see they've finally changed the memcpy() specification to forbid overlapping memory areas completely.

Previously, it was defined to copy from low addresses to high addresses, which meant you could use this to fill an array:

    p[0] = 42;
    memcpy(&p[1], &p[0], sizeof(p)-sizeof(*p));
And people did.


The original 1989 ANSI C specification stated, in "4.11.2.1 The memcpy function":

If copying takes place between objects that overlap, the behavior is undefined.

So it has been this way in C since the first official standard. I don't have a first edition K&R so I can't see what that said, though.


Huh. You're quite right.

Well, it may have been undefined, but in practice the behaviour was standardised and couldn't be changed without breaking existing code... which is basically C in a nutshell.


Code like your example would have been broken by real standard library implementations very early in the piece, because even a forward-copying implementation that does word-at-a-time copies (a straightforward and obvious optimisation with real and significant benefits on many machines of the era) would have broken it.


> The real question is: why are there two versions?

Because long long ago, when CPU was slow, and compiler and stdlib were stupid, there were two versions.


Emacs gets a bad rap as a "kitchen sink" program, but it isn't really, any more than something like the Bourne shell or Python. The majority of Emacs' built-ins are things like an elisp interpreter, some text processing stuff, and system libraries. Conversely, most of the really interesting things you can do with Emacs, like web browsing (eww), git repo management (magit), terminal emulation (ansi-term), etc. are really just programs written in elisp. Some of them also happen to be distributed with Emacs.


>"saturating" behavior is something you want only rarely.

Well, yes, because what you almost always actually want is the mathematically accurate result. Failing that, though, I can think of a few cases where you would want overflow to saturate (hardware outputs, mostly); I haven't thought of any cases yet where wrapping would be remotely useful.


Literally the definition of the integers is that (x + 1) != x. Wrapping is bad enough, but at least tends to makes failures noticeable. Saturating arithmetic makes it really hard to see that a failure has occurred.

Depending on the performance penalty you're willing to take, there's good alternatives to wrapping, such as: A) 64-bit integers, B) bigints, C) crashing on overflow.

Sure, saturation might be useful in certain circumstances, like manipulating audio samples, but it's generally something you want to make explicit. Using it everywhere? It's madness!


Say you're computing a buffer size with untrusted inputs.

With saturated arithmetic, you could add the (unsigned) sizes together without a possibility of an overflow, so you could eliminate all except the last range check (=branch).

If the end result is larger than what is reasonable, return an error. It's not possible that the value wrapped around and later code will be writing to unallocated memory.


That doesn't actually eliminate range checks. Hardware doesn't have native saturating overflow operations so the saturating overflow methods are implemented by doing a checked overflow and using the min/max value of the type if it overflows/underflows. Which is to say, it removes them from your code, but there's no performance benefit to it.


>Hardware doesn't have native saturating overflow operations

Uh, what hardware are you talking about? x86 and ARM at the least have saturating arithmetic instructions.


They do? I admit I'm not an expert on either x86 or ARM assembly, but I've never seen code that used a saturating arithmetic instruction, and the saturating math in Rust is implemented using checked arithmetic as opposed to any kind of llvm intrinsic for saturating math. Looking at the LLVM docs I don't see any kind of intrinsic for saturating math either (checked math uses llvm.sadd.with.overflow.* and friends).


It's part of the vector ISAs for both. x86 does as part of SSE (or SSE2, or MMX, etc, I don't remember). ARM it's part of the DSP extensions.


How easy it is to decide for others if they are mad or not, even not asking them about details... No worries, I'm not proposing it as default for all, I was just asking if it's possible to set as flag for some programms.


One trivial case where wrapping is desired is in calculating hashes.


>You may be able to use "greening" here, but at least AFAIK it is not common in English since it seems a bit strange to turn adjectives into nouns like that.

I think the equivalent in English would be "begreening." It looks weird written out, but I think your meaning would be clear if you said something like, "The city's begreening project will cost an estimated 1.5 million dollars."


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: