Well that is how it mostly worked until recently... unless if the developer copied and pasted from stackoverflow without understanding much. Which did happen.
It depends on the use case, but do you consider NaN to be equal to NaN? For an assert macro, I would expect so. Also, your code works differently for very large and very small numbers, eg. 1.0000001, 1.0000002 vs 1e-100, 1.0000002e-100.
For my own soft-floating point math library, I expect the value is off by a some percentage, not just off by epsilon. And so I have my own almostSame method [1] which accounts for that and is quite a bit more complex. Actually multiple such methods. But well, that's just my own use case.
For the few days without wind, natural gas is cheaper than nuclear. There is also biogas and hydro. Nuclear is not cheap to turn on off. Also, the insurance cost of nuclear power is not accounted for: basically, there is no insurance, and the state (the population) just have to live with the risk.
Natural gas emits CO2. The risks of climate change caused by CO2 utterly dwarf those of any nuclear reactor. Nuclear power in the US has the lowest deaths per joule of all of them.
There is a programming language that is reversible: Janus [1]. You could write a (lossless) data compression algorithm in this language, and if run in reverse this would uncompress. In theory you could do all types of computation, but the "output" (when run forward) would need to contain the old state. With reversible computing, there is no erased information. Landauer's principle links information theory with thermodynamics: putting order to things necessarily produces heat (ordering something locally requires "disorder" somewhere else). That is why Toffoli gates are so efficient: if the process is inherently reversible, less heat need to be produced. Arguably, heat is not "just" disorder: it is a way to preserve the information in the system. The universe is just one gigantic reversible computation. An so, if we all live in a simulation, maybe the simulation is written in Janus?
Yes you can do compression, if the text is compressible. The playground [1] has a "run length encoding" example.
Maybe you meant sorting. You can implement sorting algorithms, as long as you store the information which entries were swapped. (To "unsort" the entries when running in reverse). So, an array that is already sorted doesn't need much additional information; one that is unsorted will require a lot of "undo" space. I think this is the easiest example to see the relation between reversible computing and thermodynamics: in thermodynamics, to bring "order" to a system requires "unorder" (heat) somewhere else.
There are also examples for encryption / decryption, but I find compression and sorting more interesting.
You could package all your data into a zip using this language but you would also have a worthless stretch of memory seemingly filled with noise / things you’re not interested in.
Why do you think so? The code example shows that you can do RLE (run length encoding) without noise / additional space. I'm pretty sure you can do zip as well. It would just be very hard to implement, but it wouldn't necessarily require that the output contains noise.
Hmm. As a physicist my intuition is that information-preserving transformations are unitary (unitary transformations are 1-to-1). If a compression algorithm is going to yield a bit string (the zip file, for example) shorter than the original it can't be 1-to-1. So it must yield the zip file and some other stuff to make up for the space saved by the compression.
Actually this is true whichever interpretation you take, give or take some knowledge around black holes.
I think hawking actually proved that this is true regardless of how black holes work due to hawking radiation.
(This is way beyond my area of expertise so excuse me that this might be a stupid idea.)
I assume the following happens: while a (small) subsystem is in "pure state" (in quantum coherence), no information flows out of this subsystem. Then, when measuring, information flows out and other information flows in, which disturbs the pure state. This collapses of the wave function (quantum decoherence). For all practical purposes, it looks like quantum decoherence is irreversible, but technically this could still be reversible; it's just that the subsystem (that is in coherence) got much, much larger. Sure, for all practical purposes it's then irreversible, but for us most of physics anyway looks irreversible (eg. black holes).
The problem is that the larger subsystem includes an observer in a superposition of states of observing different measured values. And we never observe this. Copenhagen interpretation doesn't deal with this at all. It just states this empirical fact.
So if I understand correctly, you are saying the observer doesn't feel like he is in a superposition (multiple states at once). Sure: I agree that observers never experience being in a superposition.
But don't think that necessarily means we are in a Many-Worlds. I rather think that we don't have enough knowledge in this area. Assuming we live in a simulation, an alternative explanation would be, that unlikely branches are not further simulated to save energy. And in this case, superposition is just branch prediction :-)
Yes, I think that's a stance many physicists take these days. Unfortunately, it's not verifiable. And we also don't have any clue how gravity (which does become relevant at our scales) would fit into this picture.
Unitarity means that information (about quantum states) is not lost, despite it appearing otherwise after a measurement. The Many-Worlds interpretation seems to be the simplest way to explain where this information has gone.
> more overloaded than Perl by someone who maybe isn't so great at clear communications.
I have the same feeling. The root of K is APL, but to avoid special characters (I assume), the same symbol has multiple meanings (overloaded), depending on eg. the position, the data type, and the context. The idea is that "programs should be short enough to fit in your head." The challenge is, similar to Perl and Regex syntax, it's very hard and often cryptic to read.
I do think a concise syntax is useful, for a programming language. But at the same time, the syntax should be readable, and that probably means that each symbol or operator must only have one meaning, and that meaning should be (more or less) obvious.
K is an array language. Even an integer is actually an array of one element. I think that makes sense for a tiny language: This is the simplest possible type system. You can even support strings, when using eg. metadata or using a heuristic like "a string is always zero terminated" (which is what I used for my tiny language).
It's clear that the symbols want to have one meaning each, for monadic and dyadic use, but that might mean quite different execution and types.
For example, & is monadic 'where' and dyadic 'min' (a logical extension of it being AND on bit-booleans), but this means you get different semantics, even if they all capture the 'where'-ness:
1 3 ~ &0 1 0 1 / when applied to a list, gives the indices of true elements
`b`d ~ &`a`b`c`d!0 1 0 1 / when applied to a dict, gives their keys
In both cases, you get that `x@&x` works, as `&x` will yield appropriate indices for `x`, but what that actually does has changed. In other languages, these would be spelled very differently, and so do seem like an overload, but sometimes it is just a point of view.
As for why it's obvious- it's not, really, but it's no less obvious than the word `where`, and you have already learnt it, as it is (as it seems to me at least) to be punned on the C syntax (same as `*`, which gives `first`).
XPath 1.0 is a pain to write queries for. XPath 2.0 adds features that make it easier to write queries. XPath 3.1 adds support for maps, arrays, and JSON.
And the default Python XPath support is severely limited, not even a full 1.0 implementation. You can't use the Python XPath support to do things like `element[contains(@attribute, 'value')]` so you need to include an external library to implement XPath.
The problem of XPath 3.1 is that it is very complex [1] - this is a long page. Compared to the 1.0 spec, it is just too complex in my view. For the open source project I'm working on, I never felt that the "old" XPath version is painful.
I think simplicity is better. That's why Json is used nowadays, and XML is not.
I would be very interested in this research... I'm trying to write a language that is simple and concise like Python, but fast and statically typed. My gutfeeling is that more concise than Python (J, K, or some code golfing language) is bad for readability, but so is the verbosity of Rust, Zig, Java.
> the article was not intended to users but to other language designers.
That might be true, but it shows the direction that Rust is talking: put in the kitchen sink, just like C++ and Scala did. And _that_ is very much important for users.
I'm not sure it shows that. Even basic features of Rust we take from granted come from concepts common users do not need to understand. Borrowing of lifetime draws from affine types, but nobody cares when writing Rust code. If in 2012 you read a similar article explaining borrow checking in academic terms you would have thought Rust would be unusably hard, which is not.
Also I do not think that adding features is always bad to the point of comparing with Scala. Most of the things the article mentions will be almost invisible to users. For example, the `!Forget` thing it mentions will just end up with users getting new errors for things that before would have caused memory leaks. What a disgrace!
Then, pattern types allow you to remove panics from code, which is super helpful in many critical contexts where Rust is used in production, even in the Linux kernel once they will bump the language version so far.
> If in 2012 you read a similar article explaining borrow checking in academic terms you would have thought Rust would be unusably hard, which is not.
Ironically, Rust was unusably hard prior to the late-2018 edition. So now your academic article has to explain both the baseline borrow checker and non-lexical lifetimes, a vast increase in complexity.
Your statement contradicts itself. It was unusable hard before non-lexical lifetimes, but they vastly increased the complexity? Then maybe what’s complex for compiler writers to implement can make the user’s life easier by lowering the complexity of their code?
Hm, you claim that Rust and Scala are not more complex to onboard than Python... but then you say you never used Python? If that's the case, how do you know? Having used both, I do think Rust is harder to onboard, just because there is more syntax that you need to learn. And Rust is a lot more verbose. And that's before you are exposed to the borrow checker.
Well, the parent wrote "I honestly just don't believe that Rust is more complex to onboard to compared to languages like Python." And you wrote "The language itself is not more complex to onboard." So... to contract Rust with Scala, I think it's clearer to write "The language itself is not more complex to onboard _than Scala_."
To that, I completely agree! Scala is one of the most complex languages, similar to C++. In terms of complexity (roughly the number of features) / hardness to onboard, I would have the following list (hardest to easiest): C++, Scala, Rust, Zig, Swift, Nim, Kotlin, JavaScript, Go, Python.
I see the confusion. ChadNauseam mentions Python to another comment of mine, where I mentioned Gleam. In your list hardest-to-easiest perhaps Gleam is even easier than Python. They literally advertise it as "the language you can learn in a day".
Thanks a lot! I wasn't aware of Gleam, it really seems simple. I probably wouldn't say "learn in a day", any I'm not sure if it's simpler than Python, but it's statically typed, and this adds some complexity necessarily.
> the only feasible compiler solutions to preventing division-by-0 errors are either: defining the behaviour, which always ends up surprising people later on, or; incredibly cumbersome or underperformant type systems/analyses which ensure that denominators are never 0.
I don't think it's very cumbersome if the compiler checks if the divisor could be zero. Some programming languages (Kotlin, Swift, Rust, Typescript...) already do something similar for possible null pointer access: they require that you add a check "if s == null" before the access. The same can be done for division (and remainder / modulo). In my own programming language, this is what I do: you can not have a division by zero at runtime, because the compiler does not allow it [1]. In my experience, integer division by a variable is not all that common in reality. (And floating point division does not panic, and integer division by a non-zero constant doesn't panic either). If needed, one could use a static function that returns 0 or panics or whatever is best.
>Some programming languages (Kotlin, Swift, Rust, Typescript...) already do something similar for possible null pointer access: they require that you add a check "if s == null" before the access.
For Rust, this is not accurate (though I don't know for the other languages). The type system instead simply enforces that pointers are non-null, and no checks are necessary. Such a check appears if the programmer opts in to the nullable pointer type.
The comparison between pointers and integers is not a sensible one, since it's easy to stay in the world of non-null pointers once you start there. There's no equivalent ergonomics for the type of non-zero integers, since you have to forbid many operations that can produce 0 even on non-0 inputs (or onerously check that they never yield 0 at runtime).
>The same can be done for division (and remainder / modulo). In my own programming language, this is what I do: you can not have a division by zero at runtime, because the compiler does not allow it... In my experience, integer division by a variable is not all that common in reality
That's another option, but I hardly find it a real solution, since it involves the programmer inserting a lot of boilerplate to handle a case that might actually never come up in most code, and where a panic would often be totally fine.
Coming back to the actual article, this is where an effect system would be quite useful: programmers who actually want to have their code be panic-free, and who therefore want or need to insert these checks, can mark their code as lacking the panic effect. But I think it's fine for division to be exposed as a panicking operation by default, since it's expected and not so annoying to use.
The syntax in Kotin is: "val name: String? = getName(); if (name != null) { println(name.length) // safe: compiler knows it's not null }"
So, there is no explicit type conversion needed.
I'm arguing for integer / and %, there is no need for an explicit "non-zero integer" type: the divisor is just an integer, and the compiler need to have a prove that the value is not zero. For places where panic is fine, there could be a method that explicitly panics in case of zero.
I agree an annotation / effect system would be useful, where you can mark sections of the code "panic-free" or "safe" in some sense. But "safe" has many flavors: array-out-of-bounds, division by zero, stack overflow, out-of-memory, endless loop. Ada SPARK allows to prove absence of runtime errors using "pragma annotate". Also Dafny, Lean have similar features (in Lean you can give a prove).
> I think it's fine for division to be exposed as a panicking operation by default
That might be true. I think division (by non-constants) is not very common, but it would be good to analyze this in more detail, maybe by analyzing a large codebase... Division by zero does cause issues sometimes, and so the question is, how much of a problem is it if you disallow unchecked division, versus the problems if you don't check.
reply