HN user

Darmani

1,553 karma

My life mission is to reduce the global cost of software maintenance by 100x.I've worked in programming tools for 15 years across 5 companies, graduated with 2 degrees at age 20 from CMU, won the "20 Under 20" Thiel Fellowship, and have a Ph. D. in programming languages from MIT. My work has been featured in the New York Times.

As a cofounder of UpToSpeed, we are solving the problem that AI is making us 10x faster at writing code, and yet our ability to understand code remains as slow as ever

As the founder of Mirdin, I turn good software engineers into great ones by turning advanced research topics into actionable software engineering advice.

Personal: www.jameskoppel.com Startup: www.up-to-speed.ai / www.auditlabs.ai Training Business: www.mirdin.com Blog: www.pathsensitive.com

Posts30
Comments369
View on HN
stephenforte1.substack.com 14d ago

The Myth of the Solo Unicorn

Darmani
5pts2
www.cc.dev 1mo ago

Show HN: Command Center, the AI coding env for people who care about quality

Darmani
69pts32
twitter.com 1y ago

AI Scientist: Is it really any good?

Darmani
2pts0
www.pathsensitive.com 2y ago

Never again compare a startup to a vitamin

Darmani
1pts0
www.pathsensitive.com 2y ago

It's Time for the "Sell Painkillers, Not Vitamins" Metaphor to Die

Darmani
2pts0
www.pathsensitive.com 3y ago

Puzzles to convince you you don't understand dependence

Darmani
31pts13
www.pathsensitive.com 3y ago

Solving the Dog-Bunny Puzzle with Program Verification Technology

Darmani
25pts3
geek.sg 3y ago

How to build a program that cannot do the wrong thing

Darmani
3pts1
www.pathsensitive.com 3y ago

Demystifying Dependence, Part 2: The Three Dimensions of Dependence

Darmani
1pts0
www.pathsensitive.com 3y ago

Demystifying Dependence, Part 1: The Nine Dependency Puzzles

Darmani
2pts0
www.linguistic-antipatterns.com 3y ago

Show HN: Linguistic Antipatterns

Darmani
27pts4
www.lesswrong.com 5y ago

Leaky Delegation: You are not a Commodity

Darmani
1pts0
www.cubix-framework.com 5y ago

Show HN: Cubix: A Haskell Framework for “One Tool, Many Languages”

Darmani
2pts0
vimeo.com 6y ago

A natural language shell based on GPT-3

Darmani
44pts7
www.pathsensitive.com 7y ago

Defunctionalize the Continuation

Darmani
236pts31
www.pathsensitive.com 7y ago

Book Review: Practical TLA+

Darmani
2pts0
futureofcoding.org 7y ago

Future of Coding: On the Maintenance of Large Software:

Darmani
4pts0
www.pathsensitive.com 7y ago

The real difference of immutability

Darmani
1pts0
www.joelonsoftware.com 7y ago

Martian Headsets (2008)

Darmani
1pts0
www.pathsensitive.com 7y ago

Mutability's secret impact

Darmani
1pts0
techcrunch.com 9y ago

WaitChatter: Learn a new language while you wait for IM replies

Darmani
1pts0
www.indiegogo.com 14y ago

Teenager Developing Algae-Based Device That Cuts Car Emissions by 90%

Darmani
2pts0
rise4fun.com 14y ago

See static analysis tools in your browser

Darmani
3pts0
www.acm.org 15y ago

Running On Empty: The Failure to Teach K–12 Computer Science in the Digital Age

Darmani
2pts0
discovermagazine.com 17y ago

Physicists Build 10 Atom Transistor

Darmani
12pts0
web.archive.org 17y ago

Highly ironic comment from Steve Yegge (scroll down to "Lame Languages")

Darmani
16pts12
www.paulgraham.com 18y ago

PG: Disconnecting Distraction

Darmani
153pts198
discovermagazine.com 18y ago

Holographic storage technology promises to let us store 100 movies on one disk

Darmani
1pts0
physorg.com 18y ago

Text Messaging Is More Expensive Than Downloading Data from Space.

Darmani
1pts0
www.paulgraham.com 18y ago

Paul Graham: Six Principles for Making New Things

Darmani
173pts118

There's a few obvious suggestions -- discuss design tradeoffs with the AI extensively, make sure you understand all your code, and understand the refactorings.

I think using Command Center can lead to much better skills growth than any other agentic coding environment. Which is a lot like saying that a shrubbery is much taller than grass, when you need your skills to grow into a tree.

This is a problem that no-one has solved. I think we might have it solved by the end of the year (we've been doing deals with a lot of universities and are being pulled in that direction),

The things humans can offer over AIs: product sense, greater context, taste. Of these, taste (really: software architecture and design) is the one that's most fundamentally about software engineering skill. I wrote recently about this at https://self-service.mirdin.com/software-design-in-the-age-o...

The big problem: it's very hard to develop enough taste to be a general without actually being in the trenches. This goes for pretty much any field, including literal war.

I've trained about 500 software engineers. But all of them were working professionals, who would take the training back on the job each week and see all the lessons playing out in their own and their coworker's code. If they were just chatting with AI and never having to get halfway through a big feature only to realize that the design was just fundamentally flawed, rate of growth would probably be much slower.

In short: lots you can do to grow faster than someone who just stares at the Claude CLI all day and never opens an editor. But how to become actually good while still doing AI coding? Unsolved problem.

Heh, personally I fixed this by just adopting the sleep cycle my body wants of going to bed at 04:00/05:00 and going up at 11:00/12:00, life is much better now when I just accept it. One approach if your life can allow it :)

That was my life in my mid-late 20's.

But as I've gotten older, my sleep schedule has only gotten more messed up. Now I consider it a victory if I manage to go to sleep before the dawn.

Very much so, obviously prefer something async if possible, just a .patch file could suffice I suppose, but could do a call to have a look if that's the only way :) Reach out to my email from my profile and we can coordinate :)

Cool, let's chat async then. Contacting you now.

Most days I don't ship 20 PRs. But I think my record is 30.

Three things made that possible.

The first, obviously, is having Command Center.

The second is that a lot of those were fixes or UX improvements under 100 lines.

The third is, no joke, not sleeping. I've had quite a few 20+ hour days in the last 6 months. Some of that is work pressure, but also I've considered getting evaluated for a broken circadian rhythm.

I'd be curious to see some of those PRs if you're saying you've essentially solved the holy paradox of "ship fast = shit code" or "ship slow = good code".

If you're serious, I'll be happy to get on a call and show you.

Hi Fizzadar,

On the one hand, it is true that the website code was pushed 0 minutes before this announcement went up.

On the other hand, I tested just now on two different phones and didn't see any issues. Can you say in more detail what you expected vs. what actually happened?

There was an occlusion issue on some smaller screens, but it's been fixed now.

Hi egamirorrim,

The basic answer is that it runs locally. If you turn telemetry off and don't use our free Gemini credits, it's trivial to verify that no traffic goes to our servers other than a tiny subscription check. For our enterprise customers, we offer a version that doesn't even do that. Everything stays between you and your model providers (and we support custom and local models).

SOC2 is still a work in progress. I'm a former security researcher with work featured in the New York Times, and I know that doing it right (and not going through Delve) takes time. I can tell you that we have passed a compliance check for a company in a highly-regulated space.

I didn't find your contact info, but I'm available at jimmy@cc.dev, and happy to discuss your needs.

Thanks!

The final moments before this launch announcement consisted of me twiddling my thumbs while waiting for our designer to upload any version he could get ready in time that is better than the previous version of our website. So we knew we'd be launching with a lot of imperfections in the visuals. Did test in mobile, but not on iPad.

In fairness, I did most of these interviews last summer, and I know some people have changed. And while I did go a fair bit outside my network to interview people, there are all sorts of hard-to-understand selection effects that come from me being me. A 21 year-old frat boy who tried doing the same kind of interviewing with the people he could find to interview would probably get different results.

But yes, that is indeed what happened. Multiple times, I'd talk to someone that I'd expect to not be reading the code at all (solo founder, mostly nontechnical), then I'd interview him in detail about his workflow and think "Huh, there was absolutely no point in there where he was reading stuff," and then I'd ask "So how much of your time is reading code?" "60, maybe 70%"

In honesty, that's not a bad idea, and we hadn't thought of that.

It's pretty expensive to measure even for small programs. It's also more of a relative than an absolute measure, i.e.: it scores two variants of the same codebase, but the raw scores aren't very meaningful on their own. So our goal had been to use this in the benchmark set we're working on when we release a standalone refactoring product.

But the more I think about this suggestion, the more I think: "Hmmm, why not?"

Ooh. The answer is probably more interesting and philosophical than you expected

I can tell you that we do extensive testing, we figured out how to objectively measure the code quality on certain benchmark problems, empirically it's extremely helpful nearly all the time.

But in the general case: it is not actually possible to guarantee this.

That's because whether a change improves the code often depends on information which is literally not present in the codebase.

Some of these are more trite. E.g.: whether a comment is helpful or redundant slop depends on the audience.

Some are deeper. E.g.: whether a piece of duplication is good or bad depends on the intent, and that is often impossible to recover from the source. https://www.pathsensitive.com/2018/01/the-design-of-software...

A simpler example: There's a function that's never called. Should it be deleted?

There's a number of factors outside the codebase that determine the answer. Including the obvious one "Not if your next prompt is going to start using it."

I started playing didgeridoo 10 years ago for precisely this reason. Sleep apnea already cured by weight loss, but I knew by air pathways were prone to it, and I never wanted it to come back.

It worked

It took me 1-2 years to learn circular breathing, but even just learning to play for 15 seconds on one breath can give the "oxygen high" from breathing so much.

This is pretty damning for OpenAI

I don't see how. Cases are decided by facts and law, not feelings -- except to the extent those feelings are probative of state of mind, which is relevant for some legal issues but not others. My understanding is that the crux of the case is on the extent to which a number of informal messages should be considered a binding contract.

Trying to go from a single admission like this to an overall legal conclusion is a lot like seeing a single line in a program and then concluding there's a bug -- without having ever seen the rest of the program. You might think "this line always crashes, " but actually it's never called (does not go to any matter at issue), or none of the terms mean what you think they mean, etc.

Sounds like a fun life :)

Have found in that roadmap things that would help me personally like making async functions dyn-compatible. Have not found the deeper stuff I thought you were hinting at.

Last year, I wanted to use the 5- line Result.flatten function, abs then found it had been stuck in experimental....for 5 years. That left me with no confidence of the language's dev velocity.

This is getting pretty funny. In this branch of the thread alone, I've seen the defenses of: (1) "Rust is fine, you're just expecting the affordances of a GC language." (2) "Rust is fine, you're just expecting the affordances of Haskell," and now (3) "Rust is fine, you're just not used to systems programming"

It's okay if your language has problems (I have plenty of criticisms of my favorite languages), but I find it odd and concerning how frequently I've seen Rust programmers try to deflect instead of engaging in criticism.

I actually have a huge systems programming background and identify as a systems programmer. C and C++ by and large do not have the problems I've written about. These things are Rust problems, not systems problems.

Oy. It is also a common experience that, when I struggle in Rust to use a pattern that's common in nearly every other language or find another way to achieve the same goal, people who know of my Haskell background call it a "Haskell pattern," and thereby avoid facing the suggestion that their favorite language is missing some pretty basic affordances.

No, boxing everything does not magically make things more dyn-compatible. It will not magically solve the issue that tokio does a whole-program transformation that does its most restrictive checking only after all local checks have been resolved. It will not magically allow more reuse between datatypes. It will solve none of the problems I encountered... because if beginner-Rust could solve any of these problems, then they would have ceased to be problems for me by the time I became intermediate.

Rust programmers tend to obsess over minimizing those tradeoffs to get abstractions that are zero-cost. So doing it “the rust way” is often very complicated and tricky to get right while satisfying the borrow checker and type system, but once found is lean, fast, clean, and safe.

You and I must be using very different definitions of "lean." For me, "complicated" and "lean" do not go together

I respect that your experience is different.

Please respect the fact that I do not understand how one can like Rust without giving up caring about modularity.

I have pretty good reason for thinking this is a possibility. Directly, because the Rust experts I asked for help did in fact advocate giving up modularity. Indirectly, in that I've seen something similar but much stronger in a different language. That culminated in a conversation with several of the creators of that language, in which they argued their language was great for generic programming...while revealing some pretty basic holes in their understanding of generic programming (while calling me a "Haskell weenie," among other abuse). I am very confident in my conclusion about this other language community and not interested in getting into the details of this event. I am just sharing where I'm coming from, in not immediately dismissing this idea as too absurd to consider even when I have no other hypothesis.

I acknowledge that you feel personally insulted by me having a hypothesis that requires a large group of people behave in ways I consider strange. I have evidence for that hypothesis, and have been unable to find a better one. I hope you can see how the comment I am responding to crosses an extra line and is directly personally insulting.

I would very much like to come away from this discussion no longer believing that Rust programminh is at odds with modularity. I have shared some fairly basic and detailed criticisms, the ones I still remember after a year out of the language. Perhaps your can play a role in offering solutions to them -- or admit that these are indeed problems your hadn't noticed before or had gotten used to

Just investigated -- looks like this works now! Yay!

For this family of examples, had been completely stymied by AsyncFnOnce not being released yet. IIRC it had been in the works for several years, was still an experimental feature when I was trying to use it, and I gave up after much frustration at trying to get a version of Rust with experimental features working under devenv (nix).

A subtraction then to my frustrations with Rust -- though I'd still be very wary of doing this, having seen how fragile higher-order functions have been in the past.

*`dyn Writer`. `impl Writer` can only be used in function parameters.

This was one of the example approaches I gave. This works...at first. The problem is that, if you want to add a new function to the Writer trait which makes Writer no longer dyn-compatible, such as, say, any async function, then you can no longer write `Box<dyn Writer>` and need to rewrite all code that uses it.

(although you can dig under the hood and specify a pinned-down Future type, covering one kind of awfulness with another)

Look up "callback hell". Basically they encourage spaghetti.

Ah. I think you're confusing the general idea of a callback with one particular style of use. "callback hell" refers to the deep indentation that occurs when trying to program in monadic style in languages without syntactic support for monads. It was mostly solved by adding async/await syntax, aka syntactic support for the continuation monad. "Callback hell" is not spaghetti in any deep sense, merely syntactically cumbersome.

But a "callback" is a more general term, sometimes a synonym for "function parameter," sometimes for more narrow kinds of function parameter (e.g.: void function, invokable once). Many people will refer to the function argument of the `map` function as a callback, but no-one would refer to that as "callback hell."

Callbacks are quite universal, and most uses do not lead at all to callback hell. I've engaged with this topic a little bit at https://us16.campaign-archive.com/?u=8b565c97b838125f69e75fb... , above the header "Serious Business."

You got solutions to your problems didn't you?

Mostly no :(

And when I did, I largely got it by figuring stuff out myself, while being told by multiple Rust experts that I either shouldn't care about the verbosity and lack of modularity, or that if I have a problem like "using the interface instead of the implementation" it must be because I'm a Haskeller.

Well, my ultimate solution was to start working on a new product, and to not use any Rust, except for some performance-heavy libraries. With the first product, the market had changed too much by the time we were ready for prime-time, and I'd put somewhere between 25% and 70% of the reason for that delay on our choice to start building new parts of the backend in Rust.

Macros are a perfectly reasonable thing to use in Rust, even if they are best avoided where possible. Exactly like unsafePerformIO.

Good comparison!

Despite that it's still probably the best language we have for a surprisingly large range of domains.

I agree with this. I just don't agree that that list of domains has a very large intersection with the set of applications.

Interesting. But when I search `"roadmap 2026" rust`, I only get results for the video game.

Am familiar with Linear Haskell (and actually went on a walk through Tokyo with one of the authors just a few months ago). IIRC: still no resources allocated to add the things that would actually make it useful. Had not been aware of most of those, except the dependently-typed ones. Cool to know about the others. Yay linear types becoming known.

You seem interesting, and I'm curious about your background now. All I can find so far is that you're from Europe and used to lead C++ in some major corporation.

It seems to be a common reflex of Rust advocates that, whenever an issue with using the language is asked about, the response is "That's just a garbage-collected code pattern" followed by "and therefore you shouldn't want it." It's happened multiple times in this thread. [Edit: and both the times I was thinking of were from you, so need to weaken that conclusion]

Aside from having vibes of "I've chosen to get hit weekly in the face with a baseball bat, but have learned to like it, and so should you" it's also seldom true.

All three of these examples are also quite easy to do with C and C++. It's not about garbage collection.

is unnecessarily insulting.

I know that it's insulting! And it doesn't make sense, because I generally think Rust programmers are smart people. But right now, it's the only explanation I've got, so it is alas necessarily insulting. So please, please, please give me a better explanation that actually makes sense.

The truth is that some things are harder in Rust but a) often those things are best avoided anyway (e.g. callbacks), and b) it's worth the trade-off because of the other good things it allows.

This sounds like the seeds of a better explanation, but it needs a lot more to actually suffice. E.g.: why are callbacks best avoided anyway, when they're virtually required for a large number of important programming patterns? (In more technical language: they're effectively the only way to eliminate duplication in non-leaf-expressions. In even more technical language: they're the way to do second-order anti-unification.)

Surely as a Haskell user of all things you must understand that sometimes making things harder is worth the trade-off. Yeay everything is pure! Great for many reasons. Now how do I add logging to this deeply nested function?

And this is a great illustration of the difference. First, you will seldom find Haskell programmers trying to argue that, actually, things like deeply-nested logging that everyone wants are actually "best avoided anyway." Second, you'll actually get a solution if you ask about them -- in this case, to either use MTL-style, to use a fixed alias for your monad stack, or that unsafePerformIO isn't actually that bad.

BTW, similar to my unpleasant conclusion for Rust above, I have another unpleasant conclusion for Haskell: Haskell is incredible for medium-sized programs, but it has its own missing modularity features that make it non-ideal for large programs (e.g.: >50k lines). But this is a much smaller problem than it sounds because Haskell is so compact that, while many projects can be huge, very few individual codebases will need to approach that size.

That "and so on" is doing a lot of work. You may accept rejecting garbage collection as a reasonable trade-off, but the bulk of the cost is coming from a much more aggressive tradeoff Rust is making with is at odds with the goals of most application code.

A deeply-baked assumption of Rust is that your memory layout is static. Dynamic memory layout is perfectly compatible with manual memory management, but Rust does not readily support it because of its demands for static memory layout.

A very easy place to see this is the difference in decorator types between Rust and other languages like Java. Java's legacy File/reader API has you write things like `new PrintWriter(new BufferedWriter(new FileWriter("foo.txt")))`, where each layer adds some functionality to the base layer. The resulting value has principal type `PrintWriter` and can be used through the `Writer` interface.

The equivalent code in Rust would give you a value of type `PrintWriter<BufferedWriter<FileWriter>>` which can only be passed to functions that expect exactly that type and not, say, a `PrintWriter<BufferedWriter<StringStream>>`. You would solve this by using a template function that takes a `T where T: Writer` parameter and gets compiled separately for every use-site, thus contributing to Rust's infamous slow build times.

It would be perfectly sane, and desirable for application code, to be able to pass around a PrintWriter value as an owned pointer to a PrintWriter struct which contains an owned pointer to a BufferedWriter struct which contains an owned pointer to a FileWriter struct. You could even have each pointer actually be to a Writer value of unknown size, and thus recover modularity.

In Rust, there is sometimes a painful and very fragile way to do this: have each writer type contain a Box<&dyn Writer>, effectively the same as the Java solution above. This works, except that, if one day you want to add a method to the Writer trait that breaks dyn-compatibility, then you will no longer be able to do this, and will need to rewrite all code that uses this type.

I think Rust would be fine for application code if it kept the borrow checker, but had greater allowance for dynamically-sized variables, or even garbage collection. The reason calling things through an interface is so tough in Rust is because doing so requires having a pointer to a value of unknown size, which involves either heap allocation or alloca(), neither of which are very happy in Rust. Many of the other things I complained about are also downstream of this decision. Affine types are useful both in high-level state management as well as in low-level memory management. But it's Rust's focus on static memory layout that really cements it as a low-level systems language, not its inclusion of borrowing.

Way back as an undergrad in 2011, I contributed to Plaid, a JVM language whose main feature is based on affine and linear types. I'm one of the very few people in the world who knew what borrowing is before Rust had it. So I know first-hand that borrow-checking is perfectly compatible with garbage collection.

(1) Oh sure, the syntax is easy. Getting it to borrow-check is somewhere between insane and impossible. As I said, I've had friends who are actual Rust experts give up trying.

(2) No, it stems from a compiler limitation (imposed in large part by the need for static memory layout), not because there's anything intrinsically buggy about doing this.

(3) Look up "dyn-compatibility", for the largest, but not the only, problem with doing this.

I have heard this reaction from others before. One of the Rust expert friends I consulted with told me "I'm not convinced you're not trying to write Haskell-style code in Rust;" I told him the patterns I was struggling with were both trivial and common in Java.

The things I found quite difficult or impossible in Rust were to me pretty basic patterns for modularity and removing duplication that it's really shocking that these complaints are not more common.

I currently have but two hypotheses for why.

First, the second problem I mentioned only comes from using tokio, which causes your top-level program to secretly be using a defunctionalized continuation data type, derived from where exactly in other files you put your await's, that might not be Send. If you're not using tokio, you won't experience that issue.

Second...I was kinda told to just give up on deduplication and have lots of copy+pasted code. This raises the very uncomfortable hypothesis that Rust afficionados are some combination of people who came to Rust early and never learned traditional software design and don't know what they're missing, and people who were raised on traditional good software engineering but then got hit with Rust's metaphorical baseball bat of lack-of-modularity over and over until they got used to being hit with a baseball bat as a normal pain of life.

I don't like either of these explanations (esp. with tokio seeming quite dominant), so I'm awaiting an explanation that makes more sense. https://xkcd.com/3210/