Hacker Newsnew | past | comments | ask | show | jobs | submit | maemre's commentslogin

Integer overflows/underflows don't affect commutativity, unless you refer to undefined behavior, which is...undefined and found only in C and C++ among common languages. 2's complement integer arithmetic is commutative for both addition and multiplication.

Thanks to BeetleB for pointing out that addition in IEEE floats is indeed commutative (I originally claimed "+0 + -0 = +0 vs. -0 + +0 = -0" which is incorrect).

As for IEEE-754 floats, addition is commutative as long as you don't care about exact bit patterns:

NaN + NaN may return different bit patterns. The result is still NaN, so the only way you can tell this apart is by bit-casting floats to ints or byte arrays.

Multiplication over floats is also commutative modulo the caveat above.


> +0 + -0 = +0 vs. -0 + +0 = -0

These are not exceptions.

First, I will note that your result above depends on the rounding mode.

Second, IEEE 754 mandates that +0 and -0 are equal (i.e. any equality operator should return True when comparing these two). Therefore both expressions are equal.

NaN has several representations in bits, but they are all "equal" to one another.[1] If an operation gives you NaN, then so will doing it commutatively. It doesn't matter that the underlying bits are the same.

[1] Except for the signaling aspect. But I believe that is preserved in commutative operations.


You're right, producing +/- 0 depends on the rounding mode and it is commutative, I forgot about that completely. I edited my comment to fix that claim.

Also, yes, +0 == -0, but they can produce different results when used in the same expression, so the distinction does matter (although this doesn't affect commutativity, which is the larger point). For example, let f(x) = 1 / x. Then f(+0) = +inf, f(-0) = -inf.

I also agree with you about NaN, that's why I mentioned having to go outside floating point numbers (bit-casting).


> (I originally claimed "+0 + -0 = +0 vs. -0 + +0 = -0" which is incorrect)

Specifically, (-0.0) + (+0.0) = (+0.0) + (-0.0) = +0.0 (assuming round-to-nearest-or-even). OTOH, (-0.0) + (-0.0) = -0.0. This has nothing to do with +0.0 == -0.0 for comparison, addition just is commutative outright[0].

0: Pedantically, I'm not sure IEEE-754 requires the specific choice of which NaN you get when you do `some_nan + a_different_nan` versus `a_different_nan + some_nan` to be commutative, but it should.


Space complexity helps characterize this: real-world computers can (arguably) emulate a linear-bounded automaton, so anything in DSPACE(O(n)) is fair game if you can wait long enough.

For the arguably part: I am assuming that the machine can access all of the input at once, so it is reasonable to expect available memory to be a multiple of the input, so you get O(n) memory.


I think this is more in the lines of mixing up concepts (the language features that enable implementing functors etc. in Haskell with functors as used in programming). I liked the JavaScript monad example in the grandparent, even if it was underwhelming. It is similar to Douglas Crockford's presentation of monads (using JavaScript, but with more interesting examples like promises).

Type classes (as a language construct) are a way of doing ad-hoc polymorphism (read run-time operator overloading) more principled, and other language features can be used in place of them to implement an _interface_ for monads, functors, etc. One example is Scala's for expressions which boil down to a series of map, flatMap, filter and forEach calls so they work with any type that implements the subset of them you need without using type classes. If you want static typing and dynamic dispatch for stuff like monads then you'd want something like type classes or F-bounded polymorphism (what I allude to with the Mappable interface below) to have that.

So, from what I see saying "run-time operator overloading can do what monads, etc. can do" would lead to "function pointers and closures can already do dynamic dispatch, what's the point of run-time operator overloading then?". Type classes are a way to have your cake and statically type-check it too (and they aren't the only way).

Just to clarify, functors, applicatives, and monads are basically interfaces for polymorphic (generic) types with useful properties (which aren't checked by the language unless you're going out of your way to use something like Agda, so that's not the point) and they turn out to model accessing and changing data in a context in a nice way. You can just call them Mappable, Liftable, and Joinable and ship them in a Java library.

I agree the language game part of it, the idea for them comes from category theory but they are different from what category theorists had in mind, and the jargon could be toned down to emphasize what they are useful for. I think the big idea is that a list (or an option) isn't the only thing that has a meaningful map and flatMap implementation, which is much simpler than "a monad is just a monoidal object in the category of endofunctors over Haskell types, what's the problem?"


>I think this is more in the lines of mixing up concepts (the language features that enable implementing functors etc. in Haskell with functors as used in programming).

You know how you can identify an fp person? They'll condescend to you and dismiss what you say.

>Type classes (as a language construct) are a way of doing ad-hoc polymorphism (read run-time operator overloading)

You're glossing over what I'm saying and drawing some superficial analogy that I am not - I'm not talking parametric polymorphism, I'm talking about monadic evaluation ie evaluation within a context. I'm talking about the same thing as dynamic binding, which is also used to implement the same exact things as monads (see the collapsing interpreters paper by Amin and Rompf).

So no I'm not confused and I'm not conflating, I am in fact making a strong claim. Again, my proof is exactly using the same language: a morphism between runtime operator overloading and lift/bind.

To give a more abstruse argument, compare Conal Elliot's compiling to categories and jax (which is immediately recognized as a monadic interpreter framework).


I misinterpreted what you said then, sorry about that.

The examples you give were helpful to understand what you mean by "runtime operator overloading"--specifically, the Amin & Rompf paper. It's been a while since I read it but looking at it again reminded me of the similar terminology they use. I agree that having a multi-staged language gives you the benefits of using monads (as in abstracting away/carrying around context).

> You know how you can identify an fp person? They'll condescend to you and dismiss what you say.

Just to note, I don't have a strong preference towards trying to do everything with a monad, stacking them, etc. and I am definitely not an "fp person".


I like this reading of the story a lot (although I didn't read it this way when I read the book originally). It reminds me of how Camus approaches to making peace with the absurd: recognizing the absurd and creating a personal meaning in order to get free from the meaninglessness (I might be totally off base with this interpretation of Camus).


I am nerd-sniped with the computability claims here. The last paragraph sums up what I think about what you wrote more broadly.

That description of a "hyper Turing machine" is pretty boring and ridiculous, such a machine trivially solves any computational problem we have because you can embed the problem into the transition function. Here is a sketch of how to do it: Let's call the heads on each tape 0, 1, ...; and let the input be on tape 0. Then the following is a machine that _decides_ a given language:

1. read the input and place the first symbol to tape 1, the second table to tape 2, ... . This takes a single step!

2. Read the whole input by reading a single symbol from each tape, and accept it if it is in the language. This can be done because the transition function can map each string to accept or reject state directly now.

Now, here is the kicker: there are uncountably many "hyper" Turing machines but only countably many strings, so almost all of these machines cannot be described, and there cannot be a universal "hyper" Turing machine. So, I don't think they are that interesting.

Note that the alphabet here is still finite, the infinity is handled by having infinitely many tapes (this is equivalent to going through the trouble of building up something like Goedel encoding). Moreover, I didn't specify the number of tapes here, but countably infinitely many tapes are enough, so you don't need to build an "uncountably-wide" tape. The point I'm trying to make is that if you let a Turing machine-like thing to have countably infinite descriptions, then the description may as well be "look up the solution" so it gets boring from that point on. We need only countably infinite descriptions because a language is countably infinite (because it is a subset of the set of all strings). If one tries to do some shenanigans like making the languages also have higher cardinalities, well just pump up the cardinality of the "hyper" Turing machine to match and you'd end up with the same proofs with slight changes.

> The alphabet our math uses is pretty much all finite. What percent of the axioms and theorems that you know are infinitely long? Shouldn't the overwhelming majority of axioms and theorems be infinitely long (and in particular map onto an uncountable set? (e.g. transcendental numbers).

As for this, we _want_ all of that (and proofs) to be finite. That gives a lot of nice structure we can work with when dealing with first-order logic and proving stuff like compactness and (in)completeness. All of our axioms and theorems are finite (assuming using FOL), we just have countably infinitely many of them (building countably infinitely many theorems is trivial, as for axioms: axiom schemas are just a countably infinite collection of axioms).

This was all to set the record straight when it comes to computability theory. If you want tamer examples of hypercomputation, there is a lot of work on oracle machines and Turing machines that can take infinitely many steps. I think those would be better examples for the "we can reason about 'supra-mathematical' entities using plain old math." claim you are making. Although, I am not buying into this because these are all mathematical entities. We do mathematics because it is an interesting endeavor, and computability is important only in that it helps us understand the math we are doing (can we prove all "true" statements? can we devise an algorithm to solve X?), anything beyond that is still math, and a lot of the notions here (Turing machine as a proxy for algorithm, using first order logic, picking a particular set of axioms) are arbitrary choices that work well so that we can do math with a foundation that won't make us lose much sleep.


thanks for the reply,

let me try and rephrase some of what you are saying to check my understanding

set of strings is countably infinite

so my "supra mathematical entity" is boring because for any given problem we could pose it can just function as a lookup table

even if you made an alphabet (strings whose characters had decimal places or something) that was bigger, then it would still be kinda boring cuz the hyper hyper turing machine would still be a lookup table (though maybe even the machine in my example could still just be a lookup table for an uncountably large language on account of having uncountably many tapes/heads).

i'm not sure if it was intended by you but my (somewhat crackpot) takeaway from your response is,

at the limit, computation and memory become the same thing (i was physics undergrad so this still feels profound to me lol (no postgrad yet maybe one day when i am richer) (and maybe also timely w/ gpt3 XD))

also i think i am still digesting notion that there is no general version of hyper turing machine


> even if you made an alphabet (strings whose characters had decimal places or something) that was bigger, then it would still be kinda boring cuz the hyper hyper turing machine would still be a lookup table (though maybe even the machine in my example could still just be a lookup table for an uncountably large language on account of having uncountably many tapes/heads).

That's pretty much what I wanted to mean. Although I called it "boring", it was not to take a jab but more so because there is not much effort to spend to understand the limitations of such a machine once you can encode the whole language into the machine's look-up table. Also, such an extension makes us lose the ability to simulate other extended machines in general. Extensions to Turing machines are interesting because of how they may alter the limits of computability (in a completely hypothetical setup) and complexity, and I had some fun when writing the response although I called that specific machine model boring because it ended up being too powerful to be interesting (from a computability theory perspective).


i had some more crackpot thoughts if you don't mind hearing them lol

the set of all strings is countably infinite like the natural numbers

however human language when actually spoken can contain recursive implicit meanings and subtext, so maybe even though it's just strings, if you made the implicit context explicit it would be uncountably infinite, much like the real numbers, even though in practice the meaning a human can grok from any given sentence is bounded like a ieee floating point number.

but i find it somewhat interesting that whereas all ieee floats have a certain amount of meaning they can carry around/a set amount of decimal places, the way we encode implicit meanings and tones into language is uneven, it's not character by character (but it can be if you add an accent or tone to a character even in non tonal languages),

like, what if you could design a number system more like human language where the decimal places somehow only showed up under certain conditions (maybe you could argue complex numbers are kinda like that, since the imaginary or real parts will appear and re apper under certain operations)

could you design a number system or an algebra in such a way that it was up for interpretation lol (or if not why not)

one thing is that you can interpret numbers in terms of geometry, but the interpretation is one to one, and thus boring compared to the interpretability of strings.

also maybe lambda calculus is somewhat interpretable in this way, and it can encode numbers and algebraic rules

maybe logical conclusion of this train of thought is the IO monad XD


> Pick any three musical notes, even on a microtonal scale, and they form a pleasing chord.

I cannot speak to the color part, but the music part doesn't ring true to me (pun intended). For example, if I pick the first, the second and the seventh of the Phyrigian scale, it sounds pretty jarring to me (like, playing E, F, and D at the same time). And, this is staying pretty much within the scales in western classical music (although I doubt anybody would play it, I bet somebody made it context). Also, extended chords with > 3 notes sound pretty fun and has been the staple of western music for a while (blues, jazz for easy-to-reach examples).


If you play those notes in a close voicing they can be jarring (although I went through a phase of composing with close clusters of tones just like that in my 20s), but if you instead play D, F up a minor third and then put the E a major seventh above the F, you end up with a chord that functions like Dmin9 (or more accurately DminAdd9). The missing fifth gets added psychoacoustically (jazz pianists have taken advantage of this for ages by playing the 3-7 of a chord in their left hand and the melody in the right hand, letting the bass cover the root and the brain of the listener add the 5).


D-E-F and its relatives are used everywhere. Examples

Pink Floyd Comfortably Numb First chord of verse

Eye of the Tiger (Survivor) First chord of verse (“Rising up” lyric)

You can say it’s a different bass note, and so it is. There is something about the chocolate-dill-cayenne example as well that if the harmony was 90% cayenne it probably wouldn’t work. Dill, it would work a bit better. Chocolate, it would work the best.

Same with the foyer. Dominant brass might be tougher than dominant evergreen or mint.


I think the answer is a combination of both and inlining. With Rc, the compiler can inline the reference count increment/decrements as well as the non-public function add_numbers and the surrounding code is simple enough that it can do dead code elimination as well as other optimizations to remove the unobservable behavior. If you switch to use Arc (which is a better equivalent of shared_ptr as it also does locking/unlocking), the generated code is more comparable (funnily enough, part of the code for drop in Arc is deliberately not inlined so it prevents this optimization [1]).

Also, if one makes add_numbers public, the generated code is much larger. I don't know why the compiler chooses not to inline it in this case, probably (really speculating here) because it can be called in multiple locations and the IR for the function is over the inlining budget.

[1]: https://doc.rust-lang.org/src/alloc/sync.rs.html#1088


Maybe they are referring to the fact that Telegram rolled their own cryptographic protocol that had a vulnerability that was fixed with a cryptic message [1] and still has some dubious choices [2]. I am not a cryptographer by any measure, so I don't whether there was a reason why they didn't pick an existing protocol like OTR. But, it doesn't make sense to me that they didn't switch to another protocol that is audited several times but released a second version of their protocol with the same criticisms.

[1]: https://words.filippo.io/dispatches/telegram-ecdh/ [2]: https://crypto.stackexchange.com/questions/31418/signal-vs-t...


Manual memory management is much more predictable (which is really nice for interactive/real-time programs), it does have nontrivial runtime overhead: malloc and free aren't free (pun slightly intended), and often costlier than a bump allocator most moving/copying GCs use (just increment a pointer and have a very branch-predictable conditional to check OOM). Also, moving/copying GCs increase data locality by bringing live objects closer, which is more cache friendly.

I agree that manual memory management is much more predictable than garbage collection, I just wanted to touch on the nuances of the memory overhead of manual memory management.

Also, if the program creates lots of short-lived objects (which is often the case), a generational garbage collector would be pretty fast because it will reclaim/move a few objects rather than deallocating many objects. An arena collector or allocating on the stack might be better (and manual methods) in this scenario though.


Rust's str also checks things like whether you're trying to read in the middle of a character. Rust also has several related string types like Path, CStr and OsStr to make sure the strings passed to the relevant APIs are well-formed. You can also use &[u8] if you want a slice of bytes or ascii string--it has several string methods implemented in the standard library as well.

I like Rust's approach of having purpose-specific string types that may differ (e.g. if your OS is using UTF-16 encoded strings but the C strings are just arrays of bytes). It guarantees that the programmer checks/converts the string is in/to the correct form, or just let the program panic with .unwrap(), when interfacing with the external world. It makes the program safe and makes the potentially expensive string conversion calls explicit. Also, I think valid UTF-8 strings is a good default.

I haven't used Zig yet (I haven't found a good use case for it), it seems closer to C both in spirit and in implementation while removing some foot guns.


Specialized string types like Path, CStr and OsStr are stdlib types though (as they should be), not built into the language (AFAIK at least). Such high level "API enforcement" types also make sense in Zig. The question is rather whether something like Rust's 'str' should be builtin or also provided in the stdlib.


Oh, that makes sense as an interesting question. I think, similar to API enforcement types, it is a good idea to separate strings from "byte strings" if the "core" language/library uses strings (which Rust does as part of panic!, one can argue that panic! or assert! should not be part of the core though). It is a hard problem, as Python's transition to Python 3 showed.

str could have a part of "core" library.


Is Rust `str` builtin?

I thought it was just a `struct str([u8]);` (a dynamically-sized type).


It is built in. It was almost switched to be exactly that before 1.0, but IIRC it provided no real upsides and several downsides. There was a PR with the discussion but I can’t find it right now.


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

Search: