563 karma · joined January 28, 2023
I'm surprised the module system creates controversy. It's a bit confusing to get one's head around at first, especially when traits are involved, but the visibility rules make a ton of sense. It quite cleanly solves the problem of how submodules should interact with visibility. I've started using the Rust conventions in my Python projects.
I have only two criticisms:
First, the ergonomics aren't quite there when you do want an object-oriented approach (a "module-struct"), which is maybe the more common usecase. However, I don't know if this is a solvable design problem, so I prefer the tradeoff Rust made.
Second, and perhaps a weaker criticism, the pub visibility qualifiers like pub(crate) seems extraneous when re-exports like pub use exist. I appreciate maybe these are necessary for ergonomics, but it does complicate the design.
There is one other piece of historical Rust design I am curious about, which is the choice to include stack unwinding in thread panics. It seems at odds with the systems programming principle usecase for Rust. But I don't understand the design problem well enough to have an opinion.
For example, classical object-oriented programming uses classes both as an encapsulation boundary (where invariants are maintained and information is hidden) and a data boundary, whereas in Rust these are separated into the module system and structs separately. This allows for complex invariants cutting across types, whereas a private member of a class can only ever be accessed within that class, including by its siblings within a module.
Another example is the trait object (dyn Trait), which allows the client of a trait to decide whether dynamic dispatch is necessary, instead of baking it into the specification of the type with virtual functions.
Notice also the compositionality: if you do want to mandate dynamic dispatch, you can use the module system to either only ever issue trait objects, or opaquely hide one in a struct. So there is no loss of expressivity.
The interesting question is whether we're happy with where the dial is right now, which direction we want to push it, and how fast --- and the underlying meaning of the article is that maybe we should be pushing it in the regulatory direction very fast indeed.
Science is not ideas: new conceptual schemes must be invented, confounding variables must be controlled, dead-ends explored. This process takes years.
Engineering is not science: kinks must be worked out, confounding variables incorporated. This process also takes years.
Technology is not engineering: the purely technical implementation must spread, become widespread and beat social inertia and its competition, network effects must be established. Investors and consumers must be convinced in the long term. It must survive social and political repercussions. This process takes yet more years.
The folder structure reflects the subdivision of code into modules. Each module may have submodules, and each module decides the visibility of its children to other modules at the same level as itself, and to its own supermodule. This is a naturally hierarchical structure, which file systems lend themselves well to. A code database would have to replicate this structure within it somehow anyway.
A non-hierarchical tag system would help model situations where you have multiple orthogonal axes along which to organise the code (as you point out). But in these cases, which axis gets the top-level hierarchy just doesn't matter. Pick one, maybe loosely informed by organisational factors or by your problem conceptualisation.
On the flipside, in situations where a stricter hierarchy would improve modularity, the tag system might _discourage_ clean crystallisation, and cause responsibilities to bleed into each other. IMO, it's more important for there to be modules at all than for their boundaries to be perfect.
Non-locality means things synchronise instantly across the universe, can go back in time in some reference frames, and yet reality _just so happens_ to censure these secret unobservable wave function components, trading quantum for classical probability so that it is impossible for us to observe the difference between a collapsed and uncollapsed state. Is this really tenable?
Strip back the metaphysical baggage and consider the basic purpose of science. We want a theoretical machine that is supplied a description about what is happening now and gives you a description of what will happen in the future. The "state" of a system is just that description. A good _scientific_ theory's description of state is minimal: it has no redundancy, and it has no extraneous unobservables.
We're leaving my area of understanding, but I believe Haag's theorem shows that the naïve approach, where the interacting and free theories share a Hilbert space, completely fails -- even stronger than that, _no_ Hilbert space could even support an interacting QFT (in the ways required by scattering theory). This is a pretty strong argument against the existence of particles except as asymptotic approximations.
Since we don't have consensus on a well-defined, non-perturbative gauge theory, mathematically speaking it's difficult to make any firm statements about what states "exist" in absolute. (I'm certain that people working on the various flavours of non-perturbative (but still heuristic) QFT -- like lattice QFT -- would have more insights about the internal structure of non-asymptotic interactions.)
If multiple comefroms to the same line are specified, the comefrom to jump to is selected randomly.
Currently I'm restructuring a big chunk of the code for readability, trying to apply good refactoring practices (work in small bits).
After a lot of frustrating experiences with Python linear optimization DSLs, I'm thinking of writing my own at some point as a side-project :)
That said, I don't remember the orders of magnitude here, the relevant question being how cold "room temperature" is with respect to the Fermi level -- a quick Google suggests quite cold, in which case the Fermi velocity should be a very good approximation.
In which case, it's interesting to realise that the motion of the electron gas comes mainly from Pauli exclusion rather than thermal noise! It's not a result I would have expected.
What definitely affects resistance is the vibration of the nuclei lattice, in which thermal energy is also stored. This vibration makes the electrons more likely to scatter. This means even in a non-superconducting metal, resistivity drops as you get colder.
The special thing about superconductors is that there's a temperature where the resistivity suddenly drops to zero. (If you look up "superconductivity resistance against temperature", you'll see some graphs showing what I mean.)
I don't know exactly the details of why this happens, but it has something to do with Cooper pairs. Electrons in these states are also sensitive to being knocked out and bumped up to regular conducting states by thermal noise.
This thermal motion is essentially random, and the electrons constantly scatter off the nuclei every which way, so it cancels out and doesn't create a net current.
So, it's less than the electrons gently move under the influence of an electric field, and more that it introduced a slight bias in the existing thermal motion.
E: To clarify in case it may have been unclear, this is unrelated to the speed of propagation of the electric field, which as the article says is the speed of light.
When we perform tasks like time estimation we're forced to use high-level mental models. But just as abstractions in code leak, so too do low-level details sometimes influence high-level considerations in unexpected ways. The "hauling" high-level description ended up being quite adequate, but the "connecting" part hid details that were relevant for the task of time estimation.
Functions are so ubiquitous we forget what they really are: a type of guarantee about the conditions under which the code within will run. Those guarantees include the availability of arguments and a place to put the return value (on the stack).
One of the key guarantees about sync functions is the call structure: one thread of execution will be in one function and one function only at any point during the program; the function will only be exited on return (or exception, or panic) or call of another function; and all the local data will be available only for the duration of that function call.
From that perspective, async functions are a _weakening_ of the procedural paradigm where it is possible to "leave behind" an instruction pointer and stack frame to be picked up again later. The ability to suspend execution isn't an additional feature, it's a missing guarantee: a generalisation.
There is always an interplay between expressiveness and guarantees in programming languages. Sometimes, it is worth removing a guarantee to create greater expressiveness. This is just an example of that.
I mentioned exceptions earlier — it's no wonder that exceptions and async both get naturally modelled in the same way (be it with monads or algebraic effects or whatever). They are both examples of weakening of procedural guarantees. Exceptions weaken the guarantee that control flow won't exit a function until it returns.
I think the practical ramifications of this are that languages that want async should be thinking about synchronous functions as a special case of suspendable functions — specifically the ones that don't suspend.
As a counterpoint, I can imagine a lot of implementation complexities. Hardware is geared towards the classical procedural paradigm, which provides an implementation foundation for synchronous procedures. The lack of that for async can partially explain why language authors often don't provide a single async runtime, but have this filled in by libraries (I'm thinking of Rust and Kotlin here).
Cognitive load is contextual. `Option<HashSet<UserId>>` is readable to someone knowledgeable in the language (`Option`, `HashSet`) and in the system (meaning of `UserId` -- the name suggests it's an integer or GUID newtype, but do we know that for sure? Perhaps it borrows conventions from a legacy system and so has more string-like semantics? Maybe users belong to groups, and the group ID is considered part of the user ID -- or perhaps to uniquely identify a user, you need both the group and user IDs together?).
What is the cognitive load of `Callable[[LogRecord, SystemDesc], int]`? Perhaps in context, `SystemDesc` is very obvious, or perhaps not. With surrounding documentation, maybe it is clear what the `int` is supposed to mean, or maybe it would be best served wrapped in a newtype. Maybe your function takes ten different `Callable`s and it would be better pulled out into an polymorphic type. But maybe your language makes that awkward or difficult. Or maybe your function is a library export, or even if it isn't, it's used in too many places to make refactoring worthwhile right now.
I also quite like newtypes for indicating pragmatics, but it is also a contextually-dependent trade-off. You may make calls to your module more obvious to read, but you also expand the module's surface area. That means more things for people writing client code to understand, and more points of failure in case of changes (coupling). In the end, it seems to me that it is less important whether you use a newtype or not, and more important to be consistent.
In fact, this very trade-off -- readability versus surface area -- is at the heart of the "small vs large functions" debate. More smaller functions, and you push your complexity out into the interfaces and relationships between functions. Fewer large functions, and the complexity is internalised inside the functions.
To me, function size is less the deciding factor [0], but rather whether your interfaces are real, _conceptually_ clean joints of your solution. We have to think at a system level. Interfaces hide complexity, but only if the system as a whole ends up easier to reason about and easier to change. You pay a cost for both interface (surface area) and implementation (volume). There should be a happy middle.
---
[0] Also because size is often a deceptively poor indicator of implementation complexity in the first place, especially when mathematical expressions are involved. Mathematical expressions are fantastic exactly because they syntactically condense complexity, but it means very little syntactic redundancy, and so they seem to be magnets for typos and oversights.
To make an analogy with programming, you can think of a matrix as a generic container type. Normally, we think of the contained type as being the real numbers, but you could replace it with anything that supports the multiplication and addition operations -- that implements the "field interface/trait/typeclass". [1]
So you can define linear algebra over any field: the real numbers of course, but also the rational numbers, or the complex numbers. Fields can also be finite. In particular, the integers modulo n is a field if n is prime. My suggestion is to consider the case n=2. [2]
It's a bit hard to get geometric intuition about what matrices on finite fields even mean, but a lot of theorems we can prove in the real case generalize, because they depend only on the field properties of the reals. That's why I suggested it -- it would make things like inverting straightforward. And that natural relationship between linear subspaces and holding certain bits constant might make search possible.
The issue (which others pointed out) is that linearity imposes so much structure that it might not seem pseudo-random anymore. But you could consider something like XORing all numbers with a randomly generated bit string -- it won't hold up if someone looks closely but might fool the cursory human eye.
---
[0] https://en.wikipedia.org/wiki/Field_(mathematics)
[1] The analogy breaks down slightly because not only must the operations be defined, but they must also obey laws, which can't be expressed in (most) programming language type systems. But if you've used Haskell before, you'll have seen this before in the fact that a Monad has to obey the Monad laws.
---
The fundamental issue is physicists use the same symbol for the physical, measurable quantity, and the function relating it to other quantities. To be clear, that isn't a criticism: it's a notational necessity (there are too many quantities to assign distinct symbols for each function). But that makes the semantics muddled.
However, there is also a lack of clarity about the semantics of "quantities". I think it is best to think of quantities as functions over an underlying state space. Functional relationships _between_ the quantities can then be reconstructed from those quantities, subject to uniqueness conditions.
This gives a more natural interpretation for the derivatives. It highlights that an expression like S(U, N, V) doesn't imply S _is_ the function, just that it's associated to it, and that S as a quantity could be associated with other functions.
---
The state space S has the structure of a differential manifold, diffeomorphic to R^n [0].
A quantity -- what in thermodynamics we might call a "state variable" -- is a smooth real-valued function on S.
An diffeomorphism between S and R^n is a co-ordinate system. Its components form the co-ordinates. Intuitively, any collection of quantities X = (X_1, ..., X_n) which uniquely labels all points in S is a co-ordinate system, which is the same thing as saying that it's invertible. [1]
Given such a co-ordinate system, any quantity Y can naturally be associated with a function f_Y : R^n -> R, defined by f_Y(x_1, ..., x_n) := Y(X^-1(x_1, ..., x_n)). In other words, this is the co-ordinate representation of Y. In physics, we would usually write that, as an abuse of notation: Y = Y(X_1, ..., X_n).
This leads to the definition of the partial derivative holding some quantities constant: you map the "held constant" quantities and the quantity in the denominator to the appropriate co-ordinate system, then take the derivative of f_Y, giving you a function which can then be mapped back to a quantity.
In that process, you have to make sure that the held constant quantities and the denominator quantity form a co-ordinate system. A lot of thermodynamic functions are posited to obey monotonicity/convexity properties, and this is why. It might be also possible to find a more permissive definition that uses multi-valued functions, similar to how Riemann surfaces are used in complex analysis.
To do that we'd probably want to be a bit more general and allow for "partial co-ordinate systems", which might also be useful for cases involving composite systems. Any collection of quantities (Y, X_1, ..., X_n) can be naturally associated with a relation [2], where (y, x_1, ..., x_n) is in the relation if there exists a point s in S such that (Y(s), X_1(s), ..., X_n(s)) = (y, x_1, ..., x_n). You can promote that to a function if it satisfies a uniqueness condition.
I think it is also possible to give a metric (Riemannian) structure on the manifold in a way compatible with the Second Law. I remember skimming through some papers on the topic, but didn't look in enough detail.
---
[0] Or half of R^n, or a quadrant maybe.
[1] The "diffeomorphism" definition also adds the condition that the inverse be smooth.
[2] Incidentally, same sense of "relation" that leads to the "relational data model"!
However, I also think there is a tendency among well-educated people in physics to dismiss philosophical questions out of hand. It's fair enough when the point is "let's focus on the physics as it's hard enough", but questions of interpretation have merit in their own right.
The speed is one — sometimes I'm in the middle of a project and realise I need to pull in another dependency. Keeping the disruption to flow at a minimum is helpful in those cases. So it's not about deployed performance, it's about mental friction.
The other is I've found Poetry liable to breaking in obscure ways. I don't fully control everything on my work machine, which I suspect is the issue. So far, rye and uv have been smooth, but that might be down to me not having used them long enough.
No sign of a Heisenberg cut has been observed so far, even as experiments involving entanglement of larger and larger molecules are performed, which makes objective-collapse theories hard to consider seriously.
Bohmian theories are nice, but require awkward adjustments to reconcile them with relativity. But more importantly, they are philosophically uneconomical, requiring many unobservable — even theoretically — entities [0].
That leaves either many-worlds or a quantum logic/quantum Bayesian interpretations as serious contenders [1]. These interpretations aren't crank fringe nonsense. They are almost inevitable outcomes of seriously considering the implications of the theory.
I will say that personally, I find many-worlds to focus excessively on the Schrödinger-picture pure state formulation of quantum mechanics. (At least to the level that I understood it — I expect there is literature on the connection with algebraic formulations, but I haven't taken the time to understand it.) So I would lean towards quantum logic–type interpretations myself.
The point of this comment was to say that many-worlds (or "multiverses", though I dislike the term) isn't nonsense. But it also isn't exactly the kind of sci-fi thing non-physicists might picture. Given how easy it is to misinterpret the term, however, I must agree with you that a self-aware science communicator would think twice about whether the term should be included, and that there may be not-so-scrupulous intentions at play here.
Quick edit: I realise the comment I've written is very technical. I'm happy to try to answer any questions. I should preface it by stating that I'm not a professional in the field, but I studied quantum information theory at a Masters level, and always found the philosophical questions of interest.
---
[0] Many people seem to believe that many-worlds also postulates the existence of unobservable parallel universes, but this isn't true. We observe the interaction of these universe's every time we observe quantum interference.
While we're here, we can clear up the misconception about "branching" — there is no branching in many-worlds, just the coherent evolution of the universal wave function. The many worlds are projections out of that wave function. They don't discretely separate from one another, either — it depends on your choice of basis. That choice is where decoherence comes in.
[1] And of course, there is the Copenhagen "interpretation" — preferred among physicists who would rather not think about philosophy. (A respectable choice.)
I implemented this in a very quick and hacky way for 32 bits. I generated a random boolean matrix M invertible in Z_2. To turn an input number x into a corresponding number y in an N-bit space, I convert x to binary and turn that into a vector of 1s and 0s, then multiply it by that randomized matrix to get y. Here are the first few y's corresponding to x=0, 1, ...:
00000000000000000000000000000000
0xx0x000xx00x0xx000xxx00xxx0x000
x0xx000xxxx0xxx00x000xxx0xx0xx0x
xx0xx00x00x00x0x0x0xx0xxx0000x0x
x0xx0x00xxx0xx0x00xx0x0xx0xx00x0
xx0xxx0000x00xx000x0x00x0x0xx0x0
00000x0x000000xx0xxx00x0xx0xxxxx
0xx0xx0xxx00x0000xx0xxx000xx0xxx
...
(Hoping the non-monospace font doesn't ruin the alignment too much.)
Which looks... random-ish? I expect that turning these into UUIDs may result in more random-looking sequences.
Not sure how to solve the problem of search with this, but the hope would be that the linear structure gives you what you need, since fixing bytes on UUIDs should correspond to considering linear subspaces of the y vectors. Perhaps this can also be used to apply lexicographic order on the corresponding x vectors (i.e.: ordering the indexes), so that you could jump to each UUID matching the search in order.
The question is what definitions will be useful and what properties you gain or give up. Being a partial function is a perfectly acceptable trade-off for mathematics, but perhaps it makes it difficult to reason about programs in some cases.
I suppose the aim of the article is to point out the issue is not one of soundness, which is useful — but I wish more emphasis had been put on the fact that it doesn't solve the question of what 1/0 should do and produced arguments with regards to that.
The number one rule for engineering domain applications is to understand the domain.
I would blame the perennial neophilia and lack of (or inadequate adoption of/respect for) standardized texts in the industry. Though, to be fair, a lot of this does come down to the rapid changes in the technology.
Iterative development is necessary for software, of course, but this should be understood as a necessity due to the medium, not as an excuse for skipping research and design. A lot of these domains (especially something as critical as accounting) should be solved problems.
Every decision is a trade-off. The trade-off here is between on the one hand the savings from additional network reinforcement, the savings from reduction in aggregate Dx/Tx costs, and increasing the optimality of placement of generation wrt load; on the other hand, the cost of renewables generation being placed in areas with lower potential, the cost of increased price instability due to smaller markets, and of course, the switching costs. There is also the question of what incentives a zonal electricity market would actually provide to renewables developers.
With regards to the network reinforcement savings, it is worth noting a few things. A major obstacle to increasing network reinforcement is not the intrinsic investment cost, but inadequate and restrictive planning, which, the grid being a natural monopoly, results in artificially constrained connection supply (not out of malice but policy failure). Just as an illustration, the way DNOs currently determine whether to pay for flexibility services or upgrade the network is done on the basis of a _5 year_ calculation (ludicrously short!). The current waiting lists for new grid connections are on the order of a decade. Fundamentally, there is a short-sightedness in the planning system, and the long term is catching up.
As for the optimality of placement of assets, price signals already exist to reflect local needs -- there isn't exactly one single price for electricity for the whole of the UK (though it's a decent approximation). Transmission and distribution costs are baked into the settlement system. For grid constraints, both distribution and transmission use of system charges vary in space and time to reflect constraints (and flexibility services also introduce a local price signal, although I have earlier expressed skepticism of the procurement process).
If these price signals exist, why don't they cause renewables generation to become more distributed across the UK? The answer is that they probably do, but that grid losses are just smaller than the increased capacity factor of building in Scotland. Grid losses (both Dx and Tx) is on the order of 10%, and wind farms in Scotland will have a capacity factor about 30-40% greater.
Finally, to touch on the incentives question. The justification for pay-as-clear pricing (which is what you refer to as paying the highest price on the system) is actually to _incentivize_ the construction of cheaper, _renewable_ and nuclear energy. Sure, it doesn't especially disincentivize the construction of marginally-priced gas plants, but it doesn't incentivize it either. You could argue that maybe power companies are keeping this market structure to profit from their renewable assets instead of moving the whole grid to renewable, except for a simple fact: there is no monopoly on power generation in the UK.
Let me be clear: I am not actually arguing against zonal pricing. There are plenty of good arguments being made by people who have studied this more closely than me. What I'm fundamentally trying to do is provide a different perspective: that there is a lower-hanging fruit in the form of improving grid planning, a point which may be argued. But it is _not_ a simple problem with an obvious solution that's only being held back due to a conspiracy of energy suppliers.