126 karma · joined May 9, 2019
It would be nice to see some performance numbers for this as well, since CPUs obviously also have branch predictors and might be able to make branches really cheap if they always take a certain branch, especially in a hot loop, where this kind of optimization is most likely to happen.
I could also imagine that the use of inline assembly for control flow makes the compiler able to do less optimizations due to having less understanding of the control flow (the effect of that is harder to test in a microbenchmark). Maybe it's okay though if it's not volatile inline assembly.
Another way to achieve something similar to this is to use C++ templates to generate multiple versions of your code where you hardcode the branches so they get removed at compile time. Of course, for N branches this requires 2^N versions of your code, so it doesn't scale well to large number of branches, but it might allow you to push a few branches out of your hot loop into a higher level where it gets tested less often.
A data race occurs when two memory accesses race, and at least one of them is a write. In C/C++, data races using non-atomic accessed are undefined behavior, and that's why those can be very bad and you definitely want to avoid those.
You can also have a data race using atomic accesses, but there the behavior (ignoring some of the issues with the C/C++ memory model) is well-defined, and therefore it is not by definition incorrect.
I would say that the Python code in the post does have a data race (since it has unsynchronized accesses to a variable, and at least one is a write). But the behavior of that code is still precisely defined (though non-deterministic), since Python just guarantees that you will see some interleaving of the individual atomic steps of each of the threads due to the GIL). Hence the data race in Python is not undefined behavior as in C/C++, and hence it does not have the same negative connotations. Basically, Python variable accesses behave like sequentially consistent atomic reads/writes in C/C++.
Note that data races in C/C++ being undefined behavior is in large part due to compiler optimizations and preventing accesses from being reordered by the compiler or the CPU. If you're working with 32-bit (or 64-bit) cacheline aligned memory accesses, then "clobbering" or "tearing" of memory accesses is not likely to be affect you, but your code can still be broken by reordering in the compiler or the CPU (either due to instruction reordering or caches/bus communication). Hence if you have a data race on say an int variable, you probably will never see 65536 there if you didn't write that value there, but you might see older/newer values than you would expect.
Coming up with the right abstractions and the right domain model is difficult (especially if you just sit down and try to come up with stuff, you're likely to get it wrong the first time around). Knowing about some of those things could help you come up with better abstractions, but it's neither necessary nor sufficient to ensure that you will.
Take dependent types for example. They allow you to express more program invariants or correctness properties in your types. But actually using them requires you to write proofs (at least, if you're using them to their full potential). And I do think that in general System F like type systems hit a nice sweet spot and are generally good enough for the stuff that you might actually want to handle on the type system level.
I've also run into similar "proof-like" situations with much simpler type systems like those of Haskell and Rust, where I was structuring my types to "make illegal states unrepresentable", but in the process ended up complicating my program due to having to match the structure of my program to the expected structure of the types. Sometimes it is nice to _not_ to have the type system enforce some of your invariants. (Such things are also doable with dependent types of course, but this is just an example of some of the tradeoffs involved).
You can also still have a shitty domain model even if you use all of those fancy tools. They just allow you to be very formal/precise about the domain model (and do perhaps encourage some more uniformity by making it more annoying to express ugly or complicated things).
I think that this pattern is common in C++ game programming as well (e.g. when using entity component systems), because using pointers or references everywhere and building complicated object graphs is a recipe for object lifetime issues.
I would be surprised if anyone could handle the mental complexity of managing ownership in arbitrary object graphs without there being some simplifying idea (like that of an ownership tree) involved, even if it's not enforced at the compiler level (as it is in Rust, but it isn't in C++).
But ultimately both of them take the "rest of the computation"/continuation and store it somewhere (i.e. on some sleeping task/callback list) to be awakened/invoked later.
It is quite conceivable that when it comes to working-from-home (and having the employer contribute to the costs that you make at home), the laws simply aren't in place yet, because we have never seen WFH on the scale that we see during the pandemic. So it is ultimately silly to try to determine what is reasonable based on the currently existing law here.
My issue is with using "unsafe" to mean "watch out, you might be using this incorrectly, but no one even knows the exact rules that you should stick to to make sure your code is correct."
Of course, having it be in the "unsafe" part of Rust is better than having it be in safe Rust. But ideally you would be able to look at any part of a Rust program and be able to tell what it does, without having to think in terms of specific compiler optimizations or hardware details. Otherwise formal verification of concurrent algorithms and data structures becomes hopeless.
The purpose of the unsafe keyword is to mark code that could cause safety issues (undefined behavior) if its expectations about the surrounding code are not met. (e.g., an unchecked array access expects its index to be in bounds).
With unlimited time and resources, it should be possible to give a mathematical proof about those unsafe blocks, showing that their expectations are met and that undefined behavior will not actually occur (by for example proving that the array index supplied will always be in bounds).
But since relaxed and consume have unclear semantics, it hard to formally reason about the correctness of any code that uses them (and it is also hard to build tools to do that checking, as mentioned).
So in an ideal world, we would have a 100% clear semantics for all parts of the language, _including_ the unsafe fragment. Unsafe should not be a place to put poorly specified languages features.
Of course, we don't live in this ideal world, but I would rather hope for fixing the memory model so that it be made completely formal and precise (if necessary, giving up some currently allowed compiler/hardware optimizations if they're more trouble than they are worth). As an extreme example, sequential consistency is easy to understand as long as you don't mix it with other types of atomic accesses and avoid non-atomic data races.
Now that I think of it a bit more, it seems to be a category error to say that a "set" or an "object" or a "dictionary" is an algebraic data type. Algebraic data types are types (values) constructed out of simpler types (values) in a certain way, and that can be deconstructed in a way that mirrors the way they were created. You can implement various APIs/interfaces on top of that, including dictionaries and sets and whatnot.
But my point was that you can do the same on top of just integers (assuming you have the common integer operations), and you could even package it up in a type safe way so that the fact that you used integers in the implementation does not leak out, assuming the language has some modularity/abstraction mechanism so that you can make stuff private to a module. But based on this it seems to be a stretch to say that dictionaries are REALLY just integers or bitstrings.
And just like you probably wouldn't want to bring "integer reasoning" to bear on dictionaries, you probably don't want to use ADT reasoning to reason about dictionaries either. Because then you are really reasoning about the concrete implementation details and generally speaking those details are hidden from clients of the dictionary (and subject to change at any time).
And if you just have unit type, empty type, sums and products, then you can only make types with finitely many inhabitants. Adding the integers to that makes you able to make any countable type, but potentially in a trivial way as described above.
Therefore just viewing the types as sets does not give a very satisfactory basis for determining whether a type is an "algebraic data type". Maybe we could do better if we consider some more aspects related to type safety, but it's not immediately obvious to me how.
It seems to me like it is impossible to do poking in a way that could not be elided by the compiler, because removing poking never alters the semantics of the program (stack overflow or guard pages are not part of the semantics of C, I believe).
Of course, in practice you'll probably get the right behavior, but I'm personally always interested in language lawyery questions. I do believe that there are cases of security issues introduced by compilers eliding memset operations on password buffers, because the compiler can tell that the result of memset is never used and so it doesn't actually clear the memory. These sorts of things are tricky to get right.
So we can construct approximations of it in the sense that we can create computers with varying, finite amounts of RAM. This might seem like a pedantic/obvious point to make, but it is actually also possible to design abstract computational machines that operate on truly infinite quantities, like a machine that has random access to an infinite, uncomputable sequence.
From a simplicity perspective, it also makes more sense to consider machines with an arbitrarily large amount of RAM (you can always hotplug more RAM), compared to setting some arbitrary limit because that is the most RAM we happen to be able to achieve in this universe.
Though it should be mentioned that just as frequently (if not more) such behavior can cause "lazy" students to feel even more discouraged, if their discouragement is what is demotivating them. I would question the premise that to motivate "lazy" students one should give them "tough love." Of course, there are some cases where it might be helpful, but even then it should never be the kind of behavior described in the article, IMO.
The mere fact that you are talking about "TV reporters" way of talking about things communicates that you have a disdain for the way such issues are commonly discussed in the media or by the general public, and correcting their statement about the study also seems to suggest that according to you really there is no gender wage gap (or it's less of an issue than people would seem to generally think), it's all just due to personal choice or preference in what type of jobs they do or something to that effect.
You can of course agree or disagree about a lot of these things, but I do think that sometimes a "mere factual correction" can have additional connotations that are much more political and not just neutral statements. It's often the opinion or suggestion behind the factual correction that causes more offense.
In the context of that video, his comments about category theorists and type theorists make a lot of sense: type theory people and (perhaps to a more limited extent) category theory people tend to be more easily sold on using a proof assistant since the kind of mathematics they do translate more easily into current proof assistants than, say, analysis or topology.
I definitely do not think that Kevin Buzzard is suggesting that type theorists and category theorists are not doing interesting and/or useful work (after all, the proof assistant he is advocating for is based on type theory). At best, he is making a sociological observation that there is a gap between the type theory/category theory community and "mainstream" mathematicians making the widespread adoption of proof assistants in mathematics more difficult.
Looking at the most successful people in any situation is not a good way to get a sense of the underlying probabilities involved. If you let 1000 people flip coins 1000 times and count the number of heads, you'll see an extraordinarily high number of heads among the most successful people.
The mere fact that there are really few people as rich as Zuckerberg indicates a big luck / network effect factor in my opinion. Zuckerberg is probably more skilled than the average person, but there are many more people like him in terms of skills (I believe), and the vast majority of them didn't get anywhere near as rich. Zuckerberg also didn't accumulate his wealth by lots of small repeated increments: he won big by Facebook becoming successful (and there was no guarantee of that, despite Zuckerberg's skills). Without that big win, I doubt that Zuckerberg would have ended up anywhere near #3.
If you just mean that a program has certain properties (like performing IO) that are not expressed in its type, then I can see what you mean. But even in Haskell, some properties like “this function might fail to terminate or throw an exception” are not expressed in the type (although you can use monads for those things in languages where all functions terminate by default). There are no distinctions between a function that performs network accesses and one that does not, or functions that never return odd numbers, etc. There are infinitely many program properties that you can come up with which are not expressible in Haskell types.
Even dependently typed languages with very expressive type systems cannot capture all possible program properties due to logical incompleteness results.
(Also, Apple didn’t exactly repurpose LLVM for Swift. LLVM is just the backend, and Swift has quite a sophisticated frontend with its own optimization passes and intermediate language. It is safe to say that a lot of compiler theory and programming language design went into writing it. As another example, the compiler for Go was basically written from scratch and does not use something like LLVM for code generation.)
I do agree that learning CS theory is no substitute for actually writing software, and doesn’t make you a good software developer by itself. And there are probably plenty of excellent software developers who couldn’t recall what a Turing machine is. That said, a good knowledge of CS fundamentals is indispensable for some types of software development.
The reason why I believe the “depressed people are just seeing the world as it really is” line of thinking is harmful is that it encourages people to believe that their depression is simply the result of their accurate world view, and that trying to change things in order to feel better would mean they have to fool themselves.
I'm not really convinced by this type of reasoning. Things do matter if people care very deeply about them. If someone murdered someone close to you (to take an extreme example), would you still say that that is fine, because in the grand scheme nothing matters? You might say that you're an irrational human being, and that's why you care. But why should we take a definition of "meaningful" that is so disjoint for everyday human experience?
What would a hypothetical world look like in which life does have meaning, according to your definition? If no such world is even imaginable, then it isn't even clear what we are talking about.
Furthermore, I think some people use the supposed "meaninglessness" of life to justify passivity. If the universe is truly meaningless, then doing nothing is as meaningful as doing something traditionally considered meaningful.
Yet, people often base their behavior on their current emotional state. That is not the behavior of someone who believes in the complete meaninglessness of everything. Clearly, that person believes that his emotions matter and are a good guide to choose which actions to take. Playing Xbox on the couch or ruminating about the meaninglessness of life being good examples of this. Such actions are choices that are ultimately derived from value judgments.
You could use many of the same arguments you’re making against dependent types against most programming language features. Why should a language have generics? Or first-class functions? Or even any kind of static typing whatsoever? Should Java not have added lambdas because it already had anonymous inner classes? You can clearly get by just fine in a lot of business applications without some of these features.
I think we should rather be discussing whether dependent types are useful for relatively simple applications, like providing type-safe database access or REST APIs in a library, rather than looking at them as tools for proving functional correctness. It’s not so clear that dependent types are too complex a solution there, especially if you look at a library like Servant, which already uses some advanced type system features, and which is actually a really practical library that is probably used in production applications as well. A library like Servant might be able to expressed more clearly with dependent types than with a hodgepodge of type system extensions. I think that wanting type-safe REST API access is already an order of magnitude more practical than model checking kernel code.