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

It seems that Anthropic's thesis is that vertical integration wins.

It's too soon to tell if that's true or not.

One of the features of vertical integration is that there will be folks complaining about it. Like the way folks would complain that it's impossible or hard to install macOS on anything other than a Mac, and impossible or hard to install anything other than macOS on a Mac. Yet, despite those complains, the Mac and macOS are successful. So: the fact that folks are complaining about Anthropic's vertical integration play does not mean that it won't be successful for them. It also doesn't mean that they are clueless


Interestingly, another front page article today is about Apple choosing to use Gemini for Siri.

A lot of the comments revolve around how much they will be locked in and how much the base models are commoditized.

Google is pretty clearly ok with being an infrastructure/service provider for all comers. Same is true for Open AI (especially via Azure?) I guess Anthropic does not want to compete like that.


Anthropic offer their API, including for tools like Opencode. It’s more expensive than Claude Code, but I don’t think it’s priced significantly differently to competitors. Obviously Apple aren’t paying API prices, and Google have a lot more to offer them, but I don’t think Anthropic would turn down that deal if they could have it. They have their models in AWS Bedrock too, and that is an option to auth with Claude Code.

I think they do see vertical integration opportunities on product, but they definitely want to compete to power everything else too.


I, personally, will churn once my CC pro subscription is up. They're harshing my vibe.

They're probably losing money on each pro subscription so they probably won't miss me!


> They're probably losing money on each pro subscription so they probably won't miss me!

looool

Maybe the LLM thing will be profitable some day?


This is a good write up and I agree with pretty much all of it.

Two comments:

- LLVM IR is actually remarkably stable these days. I was able to rebase Fil-C from llvm 17 to 20 in a single day of work. In other projects I’ve maintained a LLVM pass that worked across multiple llvm versions and it was straightforward to do.

- LICM register pressure is a big issue especially when the source isn’t C or C++. I don’t think the problem here is necessarily licm. It might be that regalloc needs to be taught to rematerialize


> It might be that regalloc needs to be taught to rematerialize

It knows how to rematerialize, and has for a long time, but the backend is generally more local/has less visibility than the optimizer. This causes it to struggle to consistently undo bad decisions LICM may have made.


> It knows how to rematerialize

That's very cool, I didn't realize that.

> but the backend is generally more local/has less visibility than the optimizer

I don't really buy that. It's operating on SSA, so it has exactly the same view as LICM in practice (to my knowledge LICM doesn't cross function boundary).

LICM can't possibly know the cost of hoisting. Regalloc does have decent visibility into cost. Hence why this feels like a regalloc remat problem to me


> to my knowledge LICM doesn't cross function boundary

LICM is called with runOnLoop() but is called after function inlining. Inlining enlarges functions, possibly revealing more invariants.


Sure. Any pass that is scoped to functions (or even loops, or basic blocks) will have increased scope if run after inlining, and most passes run after inlining.

In the context of this thread, your observation is not meaningful. The point is: LICM doesn't cross function boundary and neither does regalloc, so LICM has no greater scope than regalloc.


"LLVM IR is actually remarkably stable these days."

I'm by no means an LLVM expert but my take away from when I played with it a couple of years ago was that it is more like the union of different languages. Every tool and component in the LLVM universe had its own set of rules and requirements for the LLVM IR that it understands. The IR is more like a common vocabulary than a common language.

My bewilderment about LLVM IR not being stable between versions had given way to understanding that this freedom was necessary.

Do you think I misunderstood?


> like the union of different languages

No. Here are two good ways to think about it:

1. It's the C programming language represented as SSA form and with some of the UB in the C spec given a strict definition.

2. It's a low level representation. It's suitable for lowering other languages to. Theoretically, you could lower anything to it since it's Turing-complete. Practically, it's only suitable for lowering sufficiently statically-typed languages to it.

> Every tool and component in the LLVM universe had its own set of rules and requirements for the LLVM IR that it understands.

Definitely not. All of those tools have a shared understanding of what happens when LLVM executes on a particular target and data layout.

The only flexibility is that you're allowed to alter some of the semantics on a per-target and per-datalayout basis. Targets have limited power to change semantics (for example, they cannot change what "add" means). Data layout is its own IR, and that IR has its own semantics - and everything that deals with LLVM IR has to deal with the data layout "IR" and has to understand it the same way.

> My bewilderment about LLVM IR not being stable between versions had given way to understanding that this freedom was necessary.

Not parsing this statement very well, but bottom line: LLVM IR is remarkably stable because of Hyrum's law within the LLVM project's repository. There's a TON of code in LLVM that deals with LLVM IR. So, it's super hard to change even the smallest things about how LLVM IR works or what it means, because any such change would surely break at least one of the many things in the LLVM project's repo.


> 1. It's the C programming language represented as SSA form and with some of the UB in the C spec given a strict definition.

This is becoming steadily less true over time, as LLVM IR is growing somewhat more divorced from C/C++, but that's probably a good way to start thinking about it if you're comfortable with C's corner case semantics.

(In terms of frontends, I've seen "Rust needs/wants this" as much as Clang these days, and Flang and Julia are also pretty relevant for some things.)

There's currently a working group in LLVM on building better, LLVM-based semantics, and the current topic du jour of that WG is a byte type proposal.


> This is becoming steadily less true over time, as LLVM IR is growing somewhat more divorced from C/C++, but that's probably a good way to start thinking about it if you're comfortable with C's corner case semantics.

First of all, you're right. I'm going to reply with amusing pedantry but I'm not really disagreeing

I feel like in some ways LLVM is becoming more like C-in-SSA...

> and the current topic du jour of that WG is a byte type proposal.

That's a case of becoming more like C! C has pointer provenance and the idea that byte copies can copy "more" than just the 8 bits, somehow.

(The C provenance proposal may be in a state where it's not officially part of the spec - I'm not sure exactly - but it's effectively part of the language in the sense that a lot of us already consider it to be part of the language.)


The C pointer provenance is still in TS form and is largely constructed by trying to retroactively justify the semantics of existing compilers (which all follow some form of pointer provenance, just not necessarily coherently). This is still an area where we have a decent idea of what we want the semantics to be but it's challenging to come up with a working formalization.

I'd have to double-check, but my recollection is that the current TS doesn't actually require that you be able to implement user-written memcpy, rather it's just something that the authors threw their hands up and said "we hope compilers support this, but we can't specify how." In that sense, byte type is going beyond what C does.


> The C pointer provenance is still in TS form and is largely constructed by trying to retroactively justify the semantics of existing compilers

That's my understanding too

> I'd have to double-check, but my recollection is that the current TS doesn't actually require that you be able to implement user-written memcpy, rather it's just something that the authors threw their hands up and said "we hope compilers support this, but we can't specify how."

That's also my understanding

> In that sense, byte type is going beyond what C does.

I disagree, but only because I probably define "C" differently than you.

"C", to me, isn't what the spec describes. If you define "C" as what the spec describes, then almost zero C programs are "C". (Source: in the process of making Fil-C, I experimented with various points on the spectrum here and have high confidence that to compile any real C program you need to go far beyond what the spec promises.)

To me, when we say "C", we are really talking about:

- What real C programs expect to happen.

- What real C compilers (like LLVM) make happen.

In that sense, the byte type is a case of LLVM hardening the guarantee that it already makes to real C programs.

So, LLVM having a byte type is a necessary component of LLVM supporting C-as-everyone-practically-it.

Also, I would guess that we wouldn't be talking about the byte type if it wasn't for C. Type safe languages with well-defined semantics have no need for allowing the user to write a byte-copy loop that does the right thing if it copies data of arbitrary type

(Please correct me if I'm wrong, this is fun)


The C standard has a conformance model that distinguishes between "strictly conforming" and "conforming" C programs. Almost zero C programs are strictly conforming, but many are conforming.

bytewise copy just works with the TS. What it does not support is tracking provenance across the copy and doing optimization based on this. What we hope is that compilers drop these optimizations, because they are unsound.

Thanks for your detailed answer. You encouraged me to give it another try and have closer look this time.

This take makes sense in the context of MLIR creation which introduces dialects which are namespaces within the IR. Given it was created by Chris Lattner I would guess he saw these problems with LLVM as well.

There is a rematerialize pass, there is no real reason to couple it with register allocation. LLVM regalloc is already somewhat subpar.

What would be neat is to expose all right knobs and levers so that frontend writers can benchmark a number of possibilities and choose the right values.

I can understand this is easier said than done of course.


> There is a rematerialize pass, there is no real reason to couple it with register allocation

The reason to couple it to regalloc is that you only want to remat if it saves you a spill


Remat can produce a performance boost even when everything has a register.

Admittedly, this comes up more often in non-CPU backends.


> Remat can produce a performance boost even when everything has a register.

Can you give an example?


Rematerializing 'safe' computation from across a barrier or thread sync/wait works wonders.

Also loads and stores and function calls, but that's a bit finicky to tune. We usually tell people to update their programs when this is needed.


> Rematerializing 'safe' computation from across a barrier or thread sync/wait works wonders.

While this is literally "rematerialization", it's such a different case of remat from what I'm talking about that it should be a different phase. It's optimizing for a different goal.

Also feels very GPU specific. So I'd imagine this being a pass you only add to the pipeline if you know you're targeting a GPU.

> Also loads and stores and function calls, but that's a bit finicky to tune. We usually tell people to update their programs when this is needed.

This also feels like it's gotta be GPU specific.

No chance that doing this on a CPU would be a speed-up unless it saved you reg pressure.


Yes.

His statement is firm and well articulated. I have nothing bad to say about the man right now


I have bad things to say about him. But they're firmly on pause. What Trump wants for the Federal Reserve is far worse.

And anyone who is a hard-currency quantity-theory-of-money conservative, should also be appalled by it.

Trump is way worse than what the harshest critics of the Federal Reserve think about it. Nobody right or left should support it. Only the billionaires will profit off the monetary disorder.


> Only the billionaires will profit off the monetary disorder.

Maybe not even them. Certainly not all of them.


By design, kiss the ring. It’s a natural progression of the kind of grifting that has been occurring through 2025: shitcoin rugpulls, tariff announcements, etc.

Porting WebKit to Fil-C

Current status: JavaScriptCore builds in the JSCOnly config and panics on start.

JSC has extensive use of uintptr_t as a carrier of pointers and that's what most of the panics are about


Why would I write code that makes it easier for a clanker to compete with me

It's acceptable to state, without evidence, that functional programming and static typing make things more reliable.

But this isn't a falsifiable claim. We cannot possibly know if this is true or not.

- Not all of banking and telecom use functional programming or even static typing.

- Functional programming often leads to write-only incomprehensible code; the exact opposite of what you need to have a reliable system.

- There's no hard evidence that static typing improves reliability. Only vibes and feels.


Wait. This doesn’t make sense to me. Statically typed programming languages cannot be deployed nor can they run with a type error that happens at runtime. Untyped languages CAN run and error out with a type error AT runtime. The inevitable consequence of that truth is this:

In the spectrum of runtime errors statically typed languages mathematically and logically HAVE less errors. That by itself is the definition of more reliable. This isn’t even a scientific thing related to falsifiability. This comes from pure mathematical logic. In science nothing can be proven, things can only be falsified. But in math and logic things can be proven and it is provable that static types are more reliable than untyped.

It is definitely not vibes and feels. Not all of banking uses statically typed languages but they are as a result living with a less reliable system then the alternative and that is a logical invariant.

There are many reasons why someone would choose untyped over typed but reliability is not a reason why they would do this unless they are ignorant.


> Statically typed programming languages cannot be deployed nor can they run with a type error that happens at runtime.

This is so completely untrue that I'm confused as to why anyone would try to claim it. Type Confusion is an entire class of error and CVE that happens in statically typed languages. Java type shenanigans are endless if you want some fun but baseline you can cast to arbitrary types at runtime and completely bypass all compile time checks.

I think the disagreement would come additionally by saying a language like Ruby doesn't actually have any type errors. Like how it can be said that GC languages can't have memory leaks. And that this model is stronger than just compile time checking. Sure you get a thing called TypeError in Ruby but because of the languages dynamism that's not an error the way it would be in C. You can just catch it and move. It doesn't invalidate the program's correctness. Ruby is so safe in it's execution model that Syntax Errors don't invalidate the running program's soundness.


> Java type shenanigans are endless if you want some fun but baseline you can cast to arbitrary types at runtime and completely bypass all compile time checks.

For this reason Java is a bad example of a typed language. It gives static typing a bad rep because of its inflexible yet unreliable type system (only basic type inference, no ADTs, many things like presence of equality not checked at compile time etc ) Something like ocaml or fsharp have much more sound and capable type systems.


Like other people replying to you C++ and Java gave types a bad rep by being so error prone and having a weak type system.

What I am saying is not untrue. It is definitive. Java just has a broken type system and it has warped your view. The article is more talking about type systems from functional programming languages where type errors are literally impossible.

You should check out elm. It’s one of the few languages (that is not a toy language and is deployed to production) where the type system is so strong that run time errors are impossible. You cannot crash an elm program because the type system doesn’t allow it. If you used that or Haskell for a while in a non trivial way it will give you deeper insight into why types matter.

> Ruby is so safe in it's execution model that Syntax Errors don't invalidate the running program's soundness.

This isn’t safety. Safety is when the program doesn’t even run or compile with a syntax error. Imagine if programs with syntax errors still tried their best effort to run… now you have a program with unknown behavior because who knows what that program did with the syntax error? Did it ignore it? Did it try to correct it? Now imagine that ruby program controlling a plane. That’s not safe.


There are different levels of static typing


> Wait. This doesn’t make sense to me. Statically typed programming languages cannot be deployed nor can they run with a type error that happens at runtime. Untyped languages CAN run and error out with a type error AT runtime. The inevitable consequence of that truth is this

There is nothing inevitable about the consequence you’re imagining because statically typed languages also reject correct programs.


It is 100 percent inevitable. Your reasoning here is illogical.

How does a statically typed language rejecting a correct program affect reliability? The two concepts are orthogonal. You’re talking about flexibility of a language but the topic is on reliability.

Let me be clear… as long as a language is Turing complete you can get it to accomplish virtually any task. In a statically typed language you have less ways to accomplish the same task then a dynamically typed language; but both languages can accomplish virtually any task. By logic a dynamically typed language is categorically more flexible than a static one but it is also categorically less reliable.


>How does a statically typed language rejecting a correct program affect reliability?

Because in some cases it will reject code that is simple and obviously correct, which will then need to be replaced by code that is less simple and less obviously correct (but which satisfies the type checker). I don't think this happens most of the time, but it does mean that static typing isn't a strict upgrade in terms of reliability. You are paying for the extra guarantees on the code you can write by giving up lots of correct programs that you could otherwise have written.


>I don't think this happens most of the time, but it does mean that static typing isn't a strict upgrade in terms of reliability.

It is a strict upgrade in reliability. You're arguing for other benefits here, like readability and simplicity. The metric on topic is reliability and NOT other things like simplicity, expressiveness or readability. Additionally, like you said, it doesn't happen "most" of the time, so even IF we included those metrics in the topic of conversation your argument is not practical.

>You are paying for the extra guarantees on the code you can write by giving up lots of correct programs that you could otherwise have written.

Again the payment is orthogonal to the benefit. The benefit is reliability. The payment is simplicity, flexibility, expressiveness, and readability. For me, personally, (and you as you've seem to indicate) programs actually become more readable and more simple when you add types. Expressiveness and flexibility is actually a foot gun, but that's not an argument I'm making as these are more opinions and therefore unprovable. You're free to feel differently.

My argument is that in the totality of possible errors, statically typed programs have provably LESS errors and thus are definitionally MORE reliable than untyped programs. I am saying that there is ZERO argument here, and that it is mathematical fact. No amount of side stepping out of the bounds of the metric "reliability" will change that.


Your definition of reliability seems different to how people use the word. I think most would consider a program that was statically checked, but often produces a wrong result as less reliable than a dynamically checked program that produces the right result.

>My argument is that in the totality of possible errors, statically typed programs have provably LESS errors and thus are definitionally MORE reliable than untyped programs. I am saying that there is ZERO argument here, and that it is mathematical fact. No amount of side stepping out of the bounds of the metric "reliability" will change that.

Making such broad statements about the real world with 100% confidence should already raise some eyebrows. Even through the lens of math and logic, it is unclear how to interpret your argument. Are you claiming that sum of all possible errors in all runnable programs in a statically checked language is less than sum of all possible errors in all runnable programs in an equivalent dynamically checked language? Both of those numbers are infinity, although i remember from school that some infinities are greater than others, I'm not sure how to prove that. And if such statement was true, how does it affect programs written in the real world?

Or is your claim that a randomly picked program from the set of all runnable statically checked programs is expected to have less errors than randomly picked program from the set of all runnable dynamically checked programs? Even this statement doesn't seem trivial, due to correct programs being rejected by type checker.

If your claim is about real world programs being written, you also have to consider that their distribution among the set of all runnable programs is not random. The amount of time, attention span and other resources is often limited. Consider the act of twisting an already correct program in various ways to satisfy the type checker, Consider the time lost that could be invested in further verifying the logic. The result will be much less clear cut, more probabilistic, more situation-dependent etc.


I think the disagreement here comes from overcomplicating what is actually a very simple claim.

I am not reasoning about infinities, cardinalities of infinite sets, or expectations over randomly sampled programs. None of that is needed. You do not need infinities to see that one set is smaller than another. You only need to show that one set contains everything the other does, plus more.

Forget “all possible programs” and forget randomness entirely. We only need to reason about possible runtime outcomes under identical conditions.

Take a language and hold everything constant except static type checking. Same runtime, same semantics, same memory model, same expressiveness. Now ask a very concrete question: what kinds of failures can occur at runtime?

In the dynamically typed variant, there exist programs that execute and then fail with a runtime type error. In the statically typed variant, those same programs are rejected before execution and therefore never produce that runtime failure. Meanwhile, any program that executes successfully in the statically typed variant also executes successfully in the dynamic one. Nothing new can fail in the static case with respect to type errors.

That is enough. No infinities are involved. No counting is required. If System A allows a category of runtime failure that System B forbids entirely, then the set of possible runtime failure states in B is strictly smaller than in A. This is simple containment logic, not higher math.

The “randomly picked program” framing is a red herring. It turns this into an empirical question about distributions, likelihoods, and developer behavior. But the claim is not about what is likely to happen in practice. It is about what can happen at all, given the language definition. The conclusion follows without measuring anything.

Similarly, arguments about time spent satisfying the type checker or opportunity cost shift the discussion to human workflow. Those may matter for productivity, but they are not properties of the language’s runtime behavior. Once you introduce them, you are no longer evaluating reliability under identical technical conditions.

On the definition of reliability: the specific word is not doing the work here. Once everything except typing is held constant, all other dimensions are equal by assumption. There is literally nothing else left to compare. What remains is exactly one difference: whether a class of runtime failures exists at all. At that point, reliability reduces to failure modes not by preference or definition games, but because there is no other remaining axis. I mean everything is the same! What else can you compare if not the type errors? Then ask the question which one is more reliable? Well… everything is the same except one has run time type errors, while the other doesn’t… which one would you call more “reliable”? The answer is obvious.

So the claim is not that statically typed languages produce correct programs or better engineers. The claim is much narrower and much stronger: holding everything else fixed, static typing removes a class of runtime failures that dynamic typing allows. That statement does not rely on infinities, randomness, or empirical observation. It follows directly from what static typing is.


Readability and simplicity can increase reliability, because simple readable code is easier to review.


From a practical perspective, most programmers will agree with me when I say that static types are more readable.


Sure. That’s a different kind of argument from the absolutist argument you were making earlier.


Right and it’s completely off topic. This is a tangent you decided to turn the conversation toward. Tangents are fine, I’m just saying that you are wrong on both the main topic and the tangent, which is also fine.


The point is that a dynamic language will in some cases enable code that is simpler and more readable (and hence probably more reliable) because sometimes the simplest code is code that wouldn’t type check. Even if statically typed languages are more readable on average, this fact invalidates your claim that statically typed languages are strictly better in terms of reliability. This can only be true if you artificially restrict attention to the subset of programs in the dynamic language that could have been statically typed.

By the way, could you tone down the rhetoric a notch?


My claim is not invalid. It’s just being evaluated against a different question.

The original post says that claims like “static typing improves reliability” are unfalsifiable and therefore just vibes. That’s false, because the claim being made is not empirical to begin with. It’s a statement about language semantics.

Holding everything else constant, static typing eliminates a class of runtime failures by construction. Programs that would fail at runtime with type errors in a dynamic language are rejected before execution in a statically typed one. That is not a hypothesis about the real world, teams, or productivity. It’s a direct consequence of what static typing is. No evidence or falsification is required.

When you argue that dynamic languages can sometimes enable simpler or more readable code that may be more reliable in practice, you’ve changed the claim. That’s a discussion about human factors and development process. It may be true, but it does not invalidate the original claim, because it addresses a different level of analysis.

Additionally in practice it isn’t true. Your entire argument flips context and is trying to point out a niche corner case to show that my overall correct argument is not absolute. You’re already wrong practically, and you’re also wrong absolutely.

So the correct framing is:

- At the language level, static typing is strictly more reliable with respect to runtime type errors.

- At the human/process level, tradeoffs exist, and outcomes can vary.

Calling the first claim “invalid” only works if you silently replace it with the second. That’s the source of the disagreement.


You can't "hold everything else constant" because the set of programs that satisfy whatever type system is a proper subset of the set of valid programs.


That’s a very long and indirect jump to make.

The claim that admitting a larger set of programs improves reliability goes through several speculative steps: that the extra programs are correct, that they are simpler, that simplicity leads to fewer mistakes, and that those mistakes would not have been caught elsewhere. None of that follows from the language semantics. It’s a human-factor argument layered on top of assumptions.

By contrast, static typing removing a class of runtime failures is immediate and unconditional. Programs that would fail at runtime with type errors simply cannot execute. No assumptions about developer skill, code style, review quality, or time pressure are needed.

Even in practice, this is why dynamic languages tend to reintroduce types via linters, contracts, or optional typing systems. The extra expressiveness doesn’t translate into higher reliability; it increases the error surface and then has to be constrained again.

So the expressiveness argument doesn’t invalidate the claim. It changes the topic. One side is a direct property of the language. The other is a speculative, multi-step causal story about human behavior. That’s why the original claim is neither unfalsifiable nor “just vibes.”

So regardless of speculative human factors, the claim stands: holding the language semantics constant, static typing strictly reduces the set of possible runtime failures, and therefore strictly increases reliability in the only direct, non-contingent sense available.


Also Did you read what I wrote? I Covered your argument here DIRECTLY in my response. It's like you read the first sentence and then responded while ignoring the next paragraph.


This logic is both too broad and rigid to be of much practical use[1]. It needs to be tightened to compare languages that are identical except for static type checks, otherwise the statically typed language could admit other kinds of errors (memory errors immediately come to mind) that many dynamic languages do not have and you would need some way of weighing the relative cost to reliability of the different categories of errors.

Even if the two languages are identical except for the static types, then it is clearly possible to write programs that do not have any runtime type errors in the dynamic language (I'll leave it as an exercise to the reader to prove this but it is very clearly true) so there exist programs in any dynamic language that are equally reliable to their static counterpart.

[1] I also disagree with your definition of reliability but I'm granting it for the sake of discussion.


You’re objecting on the wrong axis.

The claim was about reliability and lack of empirical evidence. Once framed that way, definitions matter. My argument is purely ceteris paribus: take a language, hold everything constant, and add strict static type checking. Once you do that, every other comparison disappears by definition. Same runtime, same semantics, same memory model, same expressiveness. The only remaining difference is the runtime error set.

Static typing rejects at compile time a strict subset of programs that would otherwise run and fail with runtime type errors. That is not an empirical claim; it follows directly from the definition of static typing. This is not hypothetical either. TypeScript vs JavaScript, or Python vs Python with a sound type checker, are real examples of exactly this transformation. The error profile is identical except the typed variant admits fewer runtime failures.

Pointing out that some dynamic programs have no runtime type errors does not contradict this. It only shows that individual programs can be equally reliable. The asymmetry is at the language level: it is impossible to deploy a program with runtime type errors in a sound statically typed language, while it is always possible in a dynamically typed one. That strictly reduces the space of possible runtime failures.

Redefining “reliability” does not change the result. Suppose reliability is expanded to include readability, maintainability, developer skill, team discipline, or development velocity. Those may matter in general, but they are not variables in this comparison. By construction, everything except typing is held constant. There is literally nothing else left to compare. All non-type-related factors are identical by assumption. What remains is exactly one difference: the presence or absence of runtime type errors. At that point, reliability reduces to failure count not as a philosophical choice, but because there is no other dimension remaining.

Between two otherwise identical systems, the one that can fail in fewer ways at runtime is more reliable. That conclusion is not empirical, sociological, or debatable. It follows directly from the setup.


> There's no hard evidence that static typing improves reliability.

I'm curious how you came to that conclusion?

https://pleiad.cl/papers/2012/kleinschmagerAl-icpc2012.pdf

https://www.deepdyve.com/lp/springer-journals/an-empirical-s...


The benefits of static typing with regard to reliability are unclear, at best:

https://danluu.com/empirical-pl/


I don't consider a human subjects study to be "hard evidence".

So, we can safely disregard these papers. They got exactly the result that they sought out to get, and the papers were published because they confirmed the preexisting groupthink.


Interesting, so you consider the entire scientific field of medicine to work without hard evidence?


You mean psychology? There’s no hard evidence there. The papers you’re citing are using human subjects in that sort of way. It’s pseudoscience at best

Medicine that involves testing human subject response to treatments is very different from the papers you’re citing and does involve falsifiable theses (usually, definitely not always).


I didn't link any studies. I'm not the person you originally replied to. I was trying to engage in your point that studies involving human subjects cannot contain hard evidence. And no I wasn't referring to psychology in my comment.


Then you’re changing context for no reason.

My point about human subjects is in the context of the linked studies.

I’m not super interested in further debating human subjects in science generally


If you're going to start citing research, you should look at the Cooley finding on verilog vs VHDL.


That's certainly an interesting data point, but it was a 90 minute programming contest, not peer-reviewed research.


The second paper shown above is firewalled.

The first paper shown above was presented at ICPC, which has a history of bias in reviewing, and typically only assigns one or two reviewers in any case.

Which makes it not surprising that the paper itself doesn't really prove anything, except that the authors themselves are good at creating dissimilar situations.

Subjects had to modify existing systems, which were provided by the experimenters.

The experimenters deliberately removed any semblance of anything that might hint at types (comments, variable names, etc.) and did who the fuck knows what else to the dynamically typed code to make it difficult to work with.

They also provided their own IDE and full environment which the participants had to use.

Now, of course, we've all seen the graphs which show that for simple problems, dynamic is better, and there's a crossover point, and, of course Cooley's experiment is on the far left of that graph, so it certainly doesn't prove that strict static typing isn't better for large programs, but it's at least honest in its approach and results, using self-selected working practitioners (and there was never any shortage of working practioners swearing by how much better VHDL is).

https://danluu.com/verilog-vs-vhdl/


I was searching for the Stefik article to argue here, thank you.


> Functional programming often leads to write-only incomprehensible code; the exact opposite of what you need to have a reliable system.

In what language?


Functional programming makes DSLs easier/possible, so you express your domain in natural domain language/constructs leading to easier comprehension, standardization, reuse, and testing, thereby improving reliability. This is the exact opposite of write-only code, DSLs are comprehensible/editable to non-programmers. With a strong enough type system these benefits accrue while ensuring the program in stays a subset of more correct programs than allowed by other compilers.

… I mean, if we’re just making global assertions :)

Gimme the “write only” compiler verified exhaustively pattern matched non-mutable clump in a language I can mould freely any day of the week. I will provide the necessary semantic layer for progress backed with compiler guarantees. That same poop pile in a lesser language may be unrecoverable.


> ... functional programming and static typing make things more reliable.

> But this isn't a falsifiable claim.

Saying "this isn't falsifiable" is a wild claim. Indeed the claim "functional programming and static typing make things more reliable" is falsifiable, as long as you admit a statistical understanding. The world is messy and experiments have noise, so what would you use if not statistics? Anecdotes?: no. Purely deductive methods?: no; we should not expect any single technique to be a silver bullet.

Good studies and analyses lay out a causal model and use strong methodologies for showing that some factor has a meaningful impact on some metric of interest. I recommend this as a starting point [1]

[1]: https://yanirseroussi.com/2016/05/15/diving-deeper-into-caus...


It improves reliability at compile time, certainly


Citation needed.


It’s amusing to see repeated “it’s a bubble” takes.

One of them will surely be right eventually!


The man who said the market's about to crash this year for the past 40 years finally proven right!


It could mean different things I guess, but here’s my take:

If you do very risky R&D in a big corpo then the risk creeps into other things: other projects might look at the R&D and say, “we will just use that when it’s done”. It’s a lazy kind of move for tech leaders to make, because it makes you look like a team player, and if the R&D goes belly up then you have someone else to blame. This ultimately leads to risky R&D in a big corpo not being compartmentalized as much as it should be. If it fails, it fails super hard with lots of fallout.

But risky R&D at a startup is compartmentalized. Nobody will say they use the output of the startup until there are signs of life, and even then healthy caution is applied.


What is the alternative?

"Use a database" isn't actionable advice because it's not specific enough


Use an SQLite database file, stream out delta updates to clients using zipped plaintext or Litestream or something.


> Languages that are not memory safe include C, C++, and assembly.

False.

C and C++ are not memory safe if you use the most common and most performant implementations, sure.

At some point these folks are going to have to accept that caveat


C and C++ as defined by their current standards are memory unsafe. You may argue that some specific implementations manage to stay as memory safe as they can get away with, but even then, features like union prevents a fully memory-safe implementation.


> C and C++ as defined by their current standards are memory unsafe.

I don’t think the spec says one way or another (but please correct me if you find verbiage indicating that the language must be memory unsafe).

It’s possible to make the whole language memory safe, including unions. It’s tricky, but possible.

Someone else mentioned Fil-C but Fil-C builds on a lot of prior art. The fact that C and C++ can be memory safe is no secret to those who understand language implementation.


By definition, C and C++ are memory safe as long as you follow the rules. The problem is that the rules cannot be automatically checked and in practice are the source of unenumerable issues from straight up bugs to subtle standards violations that trigger the optimizer to rewrite your code into what you didn’t intend.

But yes, fil-c is a huge improvement (afaik though it doesn’t solve the UB problem - it just guarantees you can’t have a memory safety issue as a result)


> By definition, C and C++ are memory safe as long as you follow the rules.

This statement doesn't make sense to me.

Memory safety is a property of language implementations, which is all about what happens when the programmer does not follow the rules.

> The problem is that the rules cannot be automatically checked and in practice are the source of unenumerable issues from straight up bugs to subtle standards violations that trigger the optimizer to rewrite your code into what you didn’t intend.

They can be automatically checked and Fil-C proves this. The prior art had already proved it before Fil-C existed.

> But yes, fil-c is a huge improvement (afaik though it doesn’t solve the UB problem - it just guarantees you can’t have a memory safety issue as a result)

Fil-C doesn't have UB. If you find anything that looks like UB to you, please file a GH issue.

Let's also be clear that you're referring to nasal demons specifically, not UB generally. In some contexts, like CPU ISAs, UB means a trap, rather than nasal demons. So let's use the term "nasal demons".

C and C++ only have nasal demons because:

- Policy decisions. For example, making signed integer addition have nasal demons is because someone wanted to cook a benchmark.

- Lack of memory safety in most implementations, combined with a refusal to acknowledge what happens when the wrong kind of memory access occurs. (Note that CPU ISAs like x86 and ARM are not memory safe, but have no nasal demons, because they do define what happens when any kind of memory access occurs.)

So anyway, Fil-C has no nasal demons, because:

- I turned off all of those silly policy decisions for cooking benchmarks.

- The memory safety means that I define what happens when the wrong kind of memory access occurs: the program gets killed with a panic.


First, let me say that I really respect the work you’re doing in fil-c. Nothing I say is intended as a knock and you’re doing fantastic engineering work moving the field forward and I hope you find success.

That’s good to know about nasal demons. Are you saying you somehow inhibit the optimizer from injecting a security vulnerability due to UB ala https://www.cve.org/CVERecord?id=CVE-2009-1897 ? I’m kinda curious how you trick LLVM into not optimizing through UB since it’s UB model is so tuned to the C/C++ standard.

Anyway, Fil-C is only currently working on (a lot of, but not all yet I think right?) Linux userspace while C and C++ as a standard language definition span a lot more environments. I agree the website should call out Fil-C as memory safe but I think it’s also fair to say that Fil-C is more an independent dialect of C/C++ (eg you do have to patch some existing software) - IMHO it’s too confusing for communicating out to say that C/C++ is memory safe and I’d rather it say something like Fil-C is memory safe or C/C++ code running under Fil-C is memory safe.

> Memory safety is a property of language implementations, which is all about what happens when the programmer does not follow the rules.

By this argument no language is memory safe because every language has bugs that can result in memory safety issues. Certainly rustc definitely has soundness issues that haven’t been fixed and I believe this is also true of Python, JavaScript, etc but I think it’s an unhelpful bar or framing of the problem. The language itself is memory safe and any safety issues within the language spec or implementation are a bug to be fixed. That isn’t true of C/C++ where there’s going to always exist environments where it’s impossible to even have a memory safe implementation (eg microcontrollers) let alone mandate one in the spec. And also fil-C does have a performance impact so some software may not ever be a good fit for it (eg video encoders/decoders). For example, a non memory safe conforming implementation of JavaScript is not possible. Same goes for safe rust, Python or Java. By comparison that isn’t true for c/c++.


At a certain point, it's a trade-off. A systems language will offer facilities that can be used to break encapsulation and abstractions, and access memory as a sequences of bytes. (Anything capable of file I/O on stock Linux can write to /proc/self/mem, for example.) The difference to (typical) C and C++ is that these facilities are less likely to be invoked by accident.

Reasonable people will disagree about what memory safety (and type safety) mean to them. Personally, bounds checking for arrays and strings, some solution for safe deallocation of memory, and an obviously correct way to write manual bounds checks is more interesting than (for example) no access to machine addresses and no FFI.

Regarding bounds checking, GNAT offers some interesting (non-standard) options: https://gcc.gnu.org/onlinedocs/gnat_ugn/Management-of-Overfl... Basically, you can write a bounds check in the most natural way, and the compiler will evaluate the check with infinite precision (or almost, to improve performance). In standard, you might end up with an exception in some corner cases where the check should pass. I wish more languages would offer something like this. Among widely used languages, only Python offers this capability because it uses infinite-precision integers.


> Are you saying you somehow inhibit the optimizer from injecting a security vulnerability due to UB ala https://www.cve.org/CVERecord?id=CVE-2009-1897 ? I’m kinda curious how you trick LLVM into not optimizing through UB since it’s UB model is so tuned to the C/C++ standard.

Yes that is inhibited. There’s no trick. LLVM (and other compilers) choose to do those stupid things by policy, and the policy can be turned off. It’s not even hard to do it.

> Fil-C is more an independent dialect of C/C++ (eg you do have to patch some existing software)

Fil-C is not a dialect. The patches are similar to what you’d have to do if you were porting a C program to a new CPU architecture or a different compiler.

> By this argument no language is memory safe because every language has bugs that can result in memory safety issues.

You rebutted this argument for me:

> any safety issues within the language spec or implementation are a bug to be fixed

Exactly this. A memory safe language implementation treats outstanding memory safety issues as a bug to be fixed.

This is what makes almost all JS implementations, and Fil-C, memory safe.


The standard(s) very often say that a certain piece of C code has undefined behavior. Having UB means that there is behavior that is not necessarily explainable by the standard. This includes e.g. the programming seemingly continuing just fine, the program crashing, or arbitrary code running as part of an exploited stack buffer overflow.

Now, certain implementations of C might give your more guarantees for some (or all) of the behavior that the standard says is undefined. Fil-C is an example of an implementation taking this to the extreme. But it's not what is meant when one just says "C." Otherwise I would be able to compile my C code with any of my standard-compliant compilers and get a memory-safe executable, which is definitely not the case.


Question: why is a union memory unsafe?

My meager understanding of unions is that they allow data of different types to be overlayed in the same area of memory, with the typical use case being for data structures that may contain different types of data (and the union typically being embedded in a struct that identifies the data type). This certainly presents problems with the interpretation of data stored in the union, but it also strikes me that the union object would have a clearly defined sized and the compiler would be able to flag any memory accesses outside of the bounds of the union. While this is clearly problematic, especially if at least one of the elements is a pointer, it also seems like the sort of problem that a compiler can catch (which is the benefit of Rust on this front).

Please correct me if I'm wrong. This sort of software development is a hobby for me (anything that I do for work is done in languages like Python).


A trivial example of this would be a tagged union that represents variants with control structures of different sizes; if the attacker can induce a confusion between the tag and the union member at runtime, they can (typically) perform a controlled read of memory outside of the intended range.

Rust avoids this by having sum types, as well as preventing the user from constructing a tag that’s inconsistent with the union member. So it’s not that a union is inherent unsafe, but that the language’s design needs to control the construction and invariants of a union.


Canonical example:

    union {
        char* p;
        long i;
    };
Then say that the attacker can write arbitrary integers into `i` and then trigger dereferences on `p`.


The standard does not assign meaning to this sequence of execution, so an implementation can detect this and abort. This is not just hypothetical: existing implementations with pointer capabilities (Fil-C, CHERI targets, possibly even compilers for IBM i) already do this. Of course, such C implementations are not widely used.

The union example is not particularly problematic in this regard. Much more challenging is pointer arithmetic through uintptr_t because it's quite common. It's probably still solvable, but at a certain point, changes the sources becomes easier, even at at scale (say if something uses the %p format specifier with sprintf/sscanf).


> The standard does not assign meaning to this sequence of execution, so an implementation can detect this and abort.

Real C programs use these kinds of unions and real C compilers ascribe bitcast semantics to this union. LLVM has a lot of heavy machinery to make sure that the programmer gets exactly what then expected here.

The spec is brain damage. You should ignore it if you want to be able to reason about C.

> This is not just hypothetical: existing implementations with pointer capabilities (Fil-C, CHERI targets, possibly even compilers for IBM i) already do this

Fil-C does not abort when you use this union. You get memory safe semantics:

- you can use `i` to change the pointer’s intval. But the capability can’t be changed that way. So if you make a mistake you’ll end up with an OOB pointer.

- you can use `i` to read the pointer’s current intval just as if you had done an ptrtoint cast.

I think CHERI also does not abort on the union itself. I think storing to `i` removes the capability bit so `p` crashes on deref.

> The union example is not particularly problematic in this regard. Much more challenging is pointer arithmetic through uintptr_t because it's quite common.

The union problem is one of the reasons why C is not memory safe, because C compilers give unions the expected structured assembly semantics, not whatever nonsense is in the spec.


He's talking about Fil-c


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

Search: