Through my own experience moving from IDE & higher-level languages to a simple textual editor and (nullable) systems languages, I've noticed that the way I read and write code is entirely different, and I can remember my "old eye." I think most people reading this view your "noise" as their signal. They get a good feeling when resolving tooling diagnostics (doubled if in an unfamiliar-to-them domain like systems programming.) It makes them feel secure. In that sense, I agree very much with the article: "I honestly think a lot of this discussion is fundamentally a misunderstanding of different perspectives rather than anything technical."
Personally, I've noticed that I pause less and write more now that I've got less tooling in my editor, and that's very enjoyable. I'd encourage anyone to try it. I have no autocomplete or "hover" info, just compilation errors presented aside the offending lines.
The business is this: Tailwind is free. Everyone uses it. People visit their docs and eventually buy some of the things they actually sell (like books, support, etc).
With LLMs, almost nobody visits their docs anymore just like folks barely visit Stackoverflow anymore (SOs traffic is down +80%). Fewer people see things they may want to buy from team Tailwind so they make less money so they implode. Plus LLMs just directly compete with their support offering.
Doesn't make much sense to me. It's literally a conversion of CSS rules to classes. Bootstrap already had a few of these as utility classes. I know it does a bit of magic in the background.
They made money off selling preset components and documentation etc, but as others have said, AI has pretty much ripped this off.
One of those things trying to monetise out of nothing because it became popular.
they had over 2M in revenue in 2024... then AI happened and it likely dried up, they staffed up during the boomtime and now are rightsizing based on the change of landscape.
> I suspect that part of the appeal is that null pointers are low-hanging fruit. They're easy to point out and relatively easy to "solve" in the type system, which can make the win feel larger than it actually is.
I agree. I find that Options are more desirable for API-design than making function-bodies easier to understand/maintain. I'm kind of surprised I don't use Maybe(T) more frequently in Odin for that reason. Perhaps it's something to do with my code-scale or design goals (I'm at around 20k lines of source,) but I'm finding multiple returns are just as good, if not better, than Maybe(T) in Odin... it's also a nice bonus to easily use or_return, or_break, or_continue etc. though at this point I wouldn't be surprised if Maybe(T) were compatible with those constructs. I haven't tried it.
To make `Maybe(T)` feel like a multiple return, you just need to do `.?` so `maybe_foo.? or_return` would just the same.
But as you say, it is more common to do the multiple return value thing in Odin and not need `Maybe` for a return values at all. Maybe is more common for input parameters or annotating foreign code, as you have probably noticed.
My feelings have evolved so much on Option types... When Swift came around I'm sure I would've opposed the information in this article. For two reasons.
1. I was a mobile dev, and I operated at the framework-level with UIKit and later SwiftUI. So much of my team's code really was book-keeping pointers (references) into other systems.
2. I was splitting my time with some tech-stacks I had less confidence in, and they happened to omit Option types.
Since then I've worked with Dart (before and after null safety,) C, C++, Rust, Go, Typescript, Python (with and without type hints,) and Odin. I have a hard time not seeing all of this as preference, but one where you really can't mix them to great effect. Swift was my introduction to Options, and there's so much support in the language syntax to help combat the very real added-friction, but that syntax-support can become a sort of friction as well. To see `!` at the end of an expression (or `try!`) is a bit distressing, even when you know today the unlikelihood (or impossibility) of that expression yielding `nil.`
I have come to really appreciate systems without this stuff. When I'm writing my types in Odin (and others which "lack" Optionals) I focus on the data. When I'm writing types in languages which borrow more from ML, I see types in a few ways; as containers with valid/invalid states, inseparably paired with initializers that operate on their machinery together. My mental model for a more featureful type-system takes more energy to produce working code. That can be a fine thing, but right now I'm enjoying the low-friction path which Odin presents, where the data is dumb and I get right to writing procedures.
Yes it's the burden of proof. That's why writing Rust is harder than C++. Or why Python is easier than anything else. As a user and customer, I'd rather pay more for reliable software though.
Odin offers a Maybe(T) type which might satisfy your itch. It's sort of a compromise. Odin uses multiple-returns with a boolean "ok" value for binary failure-detection. There is actually quite a lot of syntax support for these "optional-ok" situations in Odin, and that's plenty for me. I appreciate the simplicity of handling these things as plain values. I see an argument for moving some of this into the type-system (using Maybe) when it comes to package/API boundaries, but in practice I haven't chosen to use it in Odin.
Maybe(T) would be for my own internal code. I would need to wrap/unwrap Maybe at all interfaces with external code.
In my view a huge value addition from plain C to Zig/Rust has been eliminating NULL pointer possibility in default pointer type. Odin makes the same mistake as Golang did. It's not excusable IMHO in such a new language.
Both Odin and Go have the "zero is default" choice. Every type must have a default and that's what zero signifies for that type. In practice some types shouldn't have such a default, so in these languages that zero state becomes a sentinel value - a value notionally of this type but in fact invalid, just like Hoare's NULL pointer, which means anywhere you didn't check for it, you mustn't assume you have a valid value of that type. Sometimes it is named "null" but even if not it's the same problem.
Even ignoring the practical consequences, this means the programmer probably doesn't understand what their code does, because there are unstated assumptions all over the codebase because their type system doesn't do a good job of writing down what was meant. Almost might as well use B (which doesn't have types).
This gets you dynamic dispatch, roughly via the C++ route (inline vtables in implementing types). This means you must always pay for this on the types which provide it, even if you rarely use the feature, removing those vtables makes it unavailable everywhere.
A lot of programmers these days want static dispatch for its ergonomic value and Odin doesn't help you there. Odin thinks we should suck it up and write alligator_lay_egg_on(gator, egg, location) not gator.lay_egg_on(egg, location)
If we decide we'd prefer to type gator->lay_egg_on(egg, location) then Odin charges us for a vtable in our Alligator type, which we didn't need or want, and then we incur a stall every time we call that because we need to go via the vtable.
Oh, nice. I have to admit I'm not all that familiar with Odin, because I've been all-in on Zig for a long time. I've been meaning to try out a game dev project in Odin for a while though, but haven't had the time.
It’s trained to (lossy) compress large amounts of data. The system prompts have leaked and it’s just instructed to be helpful, right? I don’t entirely disagree with your sentiment, though. It’s brute force.
Personally, I've noticed that I pause less and write more now that I've got less tooling in my editor, and that's very enjoyable. I'd encourage anyone to try it. I have no autocomplete or "hover" info, just compilation errors presented aside the offending lines.
reply