HN user

maemre

189 karma

I teach and do research in programming languages. Feel free to reach me if you want to discuss any ideas about program analysis, safety, compilers, etc. (<drop the a from the username>@usfca.edu)

https://maemre.com

Posts3
Comments68
View on HN

Out of curiosity, I tried this on several Chinese LLM's via OpenRouter, only on providers promising ZDR, and repeated it a few times. Here are the results:

- DeepSeek V4 Flash and Pro refused to answer this along the lines of: "I am sorry, I cannot answer that question. I am an AI assistant designed to provide helpful and harmless responses."

- Kimi K3: Listed several dates, including 1989, and called it "Tiananmen Square massacre".

- GLM 5.2: Also listed several dates. Called the one in 1989 "The Tiananmen Square protests in Beijing, China end with military action".

- Qwen 3.6 35B A3B (I couldn't find a provider with ZDR policy for larger/newer Qwen models; they are available only through Alibaba): listed the different dates like GLM 5.2 and Kimi K3, but called it "Events in Beijing, China, commonly referred to as the Tiananmen Square protests and their aftermath." describing it in a deliberately milquetoast language.

I made another test with different providers on OpenRouter for the DeepSeek models, and they respond in other different ways such as omitting the year 1989, and refusing by saying "The Tiananmen Square protests in Beijing, China end with military action", so part of the censorship seems to be depending on the provider.

The law is written so broadly, I think it applies to them already: https://leginfo.legislature.ca.gov/faces/billTextClient.xhtm...

(c) “Application” means a software application that may be run or directed by a user on a computer, a mobile device, or any other general purpose computing device that can access a covered application store or download an application.

This is basically any program.

(e) (1) “Covered application store” means a publicly available internet website, software application, online service, or platform that distributes and facilitates the download of applications from third-party developers to users of a computer, a mobile device, or any other general purpose computing that can access a covered application store or can download an application.

This would include any package manager like dnf/apt/pacman/etc. They facilitate download of applications from third parties.

(g) “Operating system provider” means a person or entity that develops, licenses, or controls the operating system software on a computer, mobile device, or any other general purpose computing device.

This sounds to me like it would include distro maintainers. They develop and/or control the OS. Also, would this include the kernel devs? How would they be responsible for the myriad of package managers.

The overall law reeks of politicians not knowing what they're legislating.

Algebra 3 years ago

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).

Algebra 3 years ago

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.

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 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 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 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).

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 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.

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).

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.

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.

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.

In a lot of math disciplines, the papers follow the Hardy-Littlewood rule, so the author names are ordered alphabetically [1]. Maybe, that's what you've been noticing. In my area (programming languages, which may be sometimes theoretical but it's mostly a mixed bag), I noticed only one group follow that convention. Others follow the "first author is the main contributor, last author is the advisor" convention you described.

[1] https://en.wikipedia.org/wiki/Academic_authorship#Authorship...

Seconded! I am the sole contributor (or the main one) on most of the git repos I am working on--they are research prototypes and I'm a PhD student so it's mostly solo work. So, I'm the only person to "blame" when looking at a piece of code, but it is a useful command that lets me find the commit and the context for why I implemented an algorithm in a certain way after a few months.

Another example: I use the nightly Rust compiler as a library, which have unstable API. git-blame helped track down which commits change the APIs I use when I'm updating the compiler version my project depends on. git-bisect is the other useful part of git for this purpose.

Any field that has to append "science" to its name usually isn't scientific e.g. political science, social science.

Although it's a bit of a side-track and you're quoting, what are your thoughts on applying this logic to "computer _science_" or "material science"?

I think it's more about the age of the field:

1. pretty old fields like physics (which just comes from the Greek word for nature), chemistry (comes from the art of making alloys) just have names describing what is studied without adding something that says "study of".

2. a lot of newer sciences (e.g. biology, geology, psychology, sociology) we use -logy as a suffix meaning "field of study", again from ancient Greek. Some of these are natural sciences, some others are social sciences.

3. in newer fields of study, rather than deriving new Latin/Greek names we just use "X science", which is not that different from what's being done in (2) A major exception to this is the medical fields (like oncology) where we still use -logy because Latin/Greek roots are still in common use in medicine.

Linear type systems often enforce that requirement by saying both branches need to use exactly the same linear values (or consume the same resources). So your example would be rejected by the type system. For example, the following function would not type check in a linearly-typed language because then branch is using x while else branch is not using x.

    -- Using Haskell-like syntax, '-o' is linear arrow, i.e. lollipop
    foo : Int -o Int -o Int
    foo x y = if y < 0 then x else 0

They are pretty useful for certain kinds of program analysis (like abstract interpretation and symbolic execution) where you need to create slightly modified copies of the program state (e.g. when you need to split the state on a conditional, or merge two states). Immutable data structures are (space) efficient for this use case. Moreover, you can combine them with hash consing for more space efficiency (only if you are creating lots of objects that share most of their data) and fast equality checking where there is only one copy a value in heap so equality checking is just object identity checking and addresses work as hash values now so hashing large objects is faster too.

The algorithms used in these analyses are usually described in terms of mathematical objects which are inherently immutable and using immutable objects makes correctness proofs easier.

When writing a piece of code, I start with immutable data structures because of the ease of mind they bring to the table (you can pass them around and you know they will not be modified so it makes reasoning and debugging easier, this is less of a problem in C++ because it has const references and const methods). Then, if an algorithm needs a mutable data structure or if the immutable data structures are too slow (e.g. the code is in the hot path) then I switch to mutable data structures. I think it is just a different mentality when approaching programming. It works for me because (a) it is the norm in my field (program analysis, or programming languages in general), and (b) I use a programming language with an ecosystem around immutable data structures (Scala).

Well, peanut allergy is lethal and can be triggered by even inhalation of peanut dust in more severe cases.

Sensitivity to sunlight is way more manageable (by applying sunscreen and covering the skin) and although kids share food and/or eat them in the same environment I don't think they're carrying UV lights to apply to other kids.

That depends on the field. Researchers are encouraged to publish artifacts with their paper in some fields to increase reproducibility. For example, most big programming language conferences (PLDI, POPL, OOPSLA etc.) let you submit your artifact, get it evaluated and award your paper with a badge noting you also have an available, working implementation.

I first heard about it in a cyberpunk video game, Shadowrun: Hong Kong, and learned today that it is real. It felt like truly something out of science fiction back when I was playing the game.

I think they will and the paper is too naive when assuming the techniques can scale and that computers can do the translation from a vague list of requirements to a precise specification. This comic[1] explains the issue well I think. There is a lot of hot new research going on in program synthesis, most of the stuff I see is about either of:

- Programming by example where we give some example inputs and outputs and let the machine synthesize a program generalizing the input-output function. It works well for small, pure mathematical functions with not so many subtle edge cases but I don't think we can make it work for the edge cases as easily, at least without human intervention that will be analogous to programming the synthesizer.

- Synthesis from specification, we give a tight spec of the function we want and all the available functions then ask the computer to synthesize the program. Some researchers made some very cool examples of this work by e.g. taking specifications encoded with dependent types and generating conforming programs. The catch here is that, if the spec is not precise enough, you may end up with a non-conforming function and who is going to write such a precise spec? The programmer!

[1] http://www.commitstrip.com/en/2016/08/25/a-very-comprehensiv...?

The sbt module I'm using for creating JARs (sbt-assembly) has this feature and I really like how seamless it makes creating executable JARs. I used it in a couple cases to create artifacts/executables for research projects that can be easily run and without many dependencies, usually just a recent enough JVM.