HN user

lock1

197 karma

a bit polyglot---both natural and unnatural kind

Posts0
Comments97
View on HN
No posts found.

Wait what? I don't get why performance improvement implies reliability and incident improvement.

For example, doing dangerous thing might be faster (no bound checks, weaker consistency guarantee, etc), but it clearly tend to be a reliability regression.

Arm AGI CPU 4 months ago

Pretty sure it stands for "Artificial abbreviation & hype GeneratIon" nowadays

It looks like it.

Based on what I observe as an occasional tutor, it looks like compiler warnings & errors are scary for newcomers. Maybe it's because it shares the same thing that made math unpopular for most people: a cold, non-negotiable set of logical rules. Which in turn some people treat warnings & errors like a "you just made a dumb mistake, you're stupid" sign rather than a helpful guide.

Weirdly enough, runtime errors don't seem to trigger the same response in newcomers.

There's no special keyword, just a "generic" type `IO<T>` defined in standard library which has a similar "tainting" property like `async` function coloring.

Any side effect has to be performed inside `IO<T>` type, which means impure functions need to be marked as `IO<T>` return. And any function that tries to "execute" `IO<T>` side effect has to mark itself as returning `IO<T>` as well.

  > Maybe in the future we don't use these things to write programs but if you think we're going to go the rest of history with just natural languages and leave all the precision to the AI, revisit why programming languages exist in the first place.

  > The LLM having the ability to write code doesn't change that we have to understand it; we just have one more entity that has to be considered in the context of writing code. e.g. sometimes the only way to get the LLM to write certain code is to feed it other code, no amount of natural language prompting will get there.
You don't exactly need to use PLs to clarify an ambiguous requirement, you can just use a restricted unambiguous subset of natural language, like what you should do when discussing or elaborating something with your coworker.

Indeed, like terms & conditions pages, which people always skip because they're written in a "legal language", using a restricted unambiguous subset of natural language to describe something is always much more verbose and unwieldy compared to "incomprehensible" mathematical notation & PLs, but it's not impossible to do so.

With that said, the previous paragraph will work if you're delegating to a competent coworker. It should work on "AGI" too if it exists. However, I don't think it will work reliably in present-day LLMs.

  > Those are both syntactically valid lines of code. (it's actually one of python's many warts). They are not ambiguous in any way. one is a number, the other is a tuple. They return something of a completely different type.
You just demonstrated how hard it is to "check" an email or text message by missing the point of my reply.
  > "Now imagine trying to spot that one missing comma among the 20kloc of code"
I assume your previous comment tries to bring up Python's dynamic typing & late binding nature and use it as an example of how it can be problematic when someone tries to blindly merge 20kloc LLM-generated Python code.

My reply, "Your example will unambiguously resolve to an error in a standard-conforming Python interpreter." tried to respond to the possibility of such an issue. Even though it's probably not the program behavior you want, Python, being a programming language, will be 100% guaranteed to interpret it unambiguously.

I admit, I should have phrased it a bit more unambiguously than leaving it like that.

Even if it's hard, you can try running a type checker to statically catch such problems. Even if it's not possible in cases of heavy usage of Python's dynamic typing feature, you can just run it and check the behavior at runtime. It might be hard to check, but not impossible.

On the other hand, it's impossible to perform a perfectly consistent "check" on this reply or an email written in a natural language, the person reading it might interpret the message in a completely different way.

Code is much much harder to check for errors than an email.

Disagree.

Even though performing checks on dynamic PLs is much harder than on static ones, PLs are designed to be non-ambiguous. There should be exactly 1 interpretation for any syntactically valid expression. Your example will unambiguously resolve to an error in a standard-conforming Python interpreter.

On the other hand, natural languages are not restricted by ambiguity. That's why something like Poe's law exists. There's simply no way to resolve the ambiguity by just staring at the words themselves, you need additional information to know the author's intent.

In other words, an "English interpreter" cannot exist. Remove the ambiguities, you get "interpreter" and you'll end up with non-ambiguous, Python-COBOL-like languages.

With that said, I agree with your point that blindly accepting 20kloc is certainly not a good idea.

  > Or is a big part of this concept only relevant for strong functional languages with sum types and pattern matching?
It need not strictly be a pure functional language for type-driven style to be usable. Type-driven style only requires the fact that some type cannot be assigned to another type, so it's kind of possible to do even in a language like C, as `int a = (struct Foo) {};` would get rejected by C compilers.

However, I don't think it's doable in languages with structural type systems like Typescript or Go's interface without a massive ergonomic hit for minimal gain. Languages with a structural type system are deliberately designed to remove the intentionality of "type T cannot be assigned to type S" in exchange for developer ergonomics.

  > However, is there a similar article but written with more common languages (C#, C++, Java, Go) in mind?
For C#, there's F#-focused article, which I believe some of it can be applied to C# as well:

F# - Railway Oriented Programming - https://fsharpforfunandprofit.com/rop/

F# - Designing with Types - https://fsharpforfunandprofit.com/series/designing-with-type...

For modern Java, there is some attempt at popularizing "Data-Oriented Programming" which just rebranded "Type-driven design". Surprisingly, with JDK 21+, type-driven style is somewhat viable there, as there is algebraic data type via `record` + `sealed` and exhaustive pattern match & destructuring.

Inside Java Blog - Data-Oriented Programming - https://inside.java/2024/05/23/dop-v1-1-introduction/

Infoq - Data-Oriented Programming - https://www.infoq.com/articles/data-oriented-programming-jav...

For Rust, due to the new mechanics introduced by its affine type system, there is much more flexibility in what you could express in Rust types compared to more common languages.

Rust - Typestate Pattern - https://cliffle.com/blog/rust-typestate/

Rust - Newtype - https://rust-unofficial.github.io/patterns/patterns/behaviou...

That's true, but then again, don't forget the fact that words might get interpreted as different things by different people. Words like "arrow", "functor", or "validate" might get interpreted slightly differently even between people with the same background.

After all, the meaning of words is just a socially accepted meaning attached to a certain arrangement of symbols. The meaning can be whatever they want it to be. And even though each individual might interpret it slightly differently, as long as the interpretation is "compatible", communication between individuals is possible.

Arguably, it's more useful to distinguish between "parse" & "validate" and I agree with that. But based on my own experience and what I've observed when I'm trying to spread type-driven style, it looks like there's no difference in meaning between the words "parse" and "validate" for most developers. Trying to sell type-driven style via the "catchy catchphrase" "Parse, Don't Validate" will certainly backfire, confusing most people rather than making them appreciate the value of it.

In my opinion, it's not worth it to combat this "parse" & "validate" misconception for the sake of the catchphrase "Parse, Don't Validate". Why? Pure FP and type-driven style already put off most people because of the tendency to go with mathematical jargon. Why add even more unnecessary barriers when the core of it is just "utilize your type system"?

I agree with the point of the "Parse, Don't Validate" article, but I strongly dislike the "catchphrase" marketing part.

  > Because in those examples you can have a User whose email property is a ParseError and you still end up having to check "is the email property result for this user type Email or type ParseError?"
In languages with a strong type system, `User` should hold `email: Option<ValidatedEmail>`. This will reject erroneous attempts `user.email = Email::parse(raw_string);` at compile time, as `Result<Email,ParseError>` is not compatible / assignable to `Option<ValidatedEmail>`.

It's kind of a "oh I forgot to check `email.isValidated`" reminder, except now being presented as an incompatible type assignment and at compile-time. Borrowing Rust's syntax, the type error can be solved with

  user.email = Email::parse(raw_string)
      .ok()
      .and_then(|wellformed_email| {
          email_service.validate_by_send_email(wellformed_email)
      });
Which more or less gets translated as "Check email well-formedness of this raw string. If it's well-formed, try to send a test email. In case of any failure during parsing or test email, leave the `user.email` field to be empty (represented with `Option::None`)".
  > and it's very similar to just checking a validation bool except it's hiding what's actually going on.
Arguably, it's the other way around. Looking back at `email: Option<ValidatedEmail>`, it's visible at compile-time `User` demands "checking validation bool", violate this and you will get a compile-time error.

On the other hand, the usual approach of assigning raw string directly doesn't say anything at all about its contract, hiding the contract of `user.email` must be a well-formed, contactable email. Not only it's possible to assign arbitrary malformed "email" string, remembering to check `email.isValidated` is also programmer due diligence, forget once and now there's a bug.

Coming from a more "average imperative" background like C and Java, outside of compiler or serde context, I don't think "parse" is a frequently used term there. The idea of "checking values to see whether they fulfill our expectations or not" is often called "validating" there.

So I believe the "Parse, Don't Validate" catchphrase means nothing, if not confusing, to most developers. "Does it mean this 'parse' operation doesn't 'validate' their input? How do you even perform 'validation' then?" is one of several questions that popped up in my head the first time I read the catchphrase prior to Haskell exposure.

Something like "Utilize your type system" probably makes much more sense for them. Then just show the difference between `ValidatedType validate(RawType)` vs `void RawType::validate() throws ParseError`.

In the spirit of "Parse, Don't Validate", rather than encode "validation" information as a boolean to be checked at runtime, you can define `Email { raw: String }` and hide the constructor behind a "factory function" that accepts any string but returns `Option<Email>` or `Result<Email,ParseError>`.

If you need a stronger guarantee than just a "string that passes simple email regex", create another "newtype" that parses the `Email` type further into `ValidatedEmail { raw: String, validationTime: DateTime }`.

While it does add some "boilerplate-y" code no matter what kind of syntactical sugar is available in the language of your choice, this approach utilizes the type system to enforce the "pass only non-malformed & working email" rule when `ValidatedEmail` type pops up without constantly remembering to check `email.isValidated`.

This approach's benefit varies depending on programming languages and what you are trying to do. Some languages offer 0-runtime cost, like Haskell's `newtype` or Rust's `repr(transparent)`, others carry non-negligible runtime overhead. Even then, it depends on whether the overhead is acceptable or not in exchange for "correctness".

  > This makes those tools, as powerful as they can be, unable to help us think about and enforce correctness across horizons that are not visible from the standpoint of a single project at a single point in time (systems distributed across time, across network, across version history).
Eh?

I don't think there's something preventing you from constructing guardrails with type system & tests enforcing correctness that handles versioning.

I'm not buying the "unable to help us to think about" part. I argue that Rust's `Option`/`Result`/sum-type + exhaustive match/switch very valuable for proper error handling. You could define your own error type and exhaustively handle each case gracefully, with the bonus that now your compiler is also able to check it.

No, there isn't.

I guess you could pick a subset of a particular natural language such that it removes ambiguity. At that point, you're basically reinventing something like COBOL or Python.

Ambiguity in natural languages is a feature, not a bug. While it's better not to be an unintentional pun or joke instruction that might get interpreted as "launch the missile" by computer.

However, each project error tolerance is different. Arguably, for an average task within the umbrella of "software engineer", even current LLMs seem good enough for most purposes. It's a kind of similar transition to automatic memory managed language, trading control for "DX".

This kind of comment reminds me of how broad "software development" is.

On other HN posts, they're stating something like "software development is dead", "LLM as a compiler", "Do you read compiled assembly?", and so on.

While some other posts like this contain huge mechanical sympathy and literally r/w the assembly directly.

Why structural is more fundamental?

C#'s anonymous type shares some flexibility of structural type system even though it still a nominal type.

  > A language would only need a single "newtype" or "nominal" keyword to create nominal types from structural types.
I think you also can add `structural` keyword & apply structural type system in generally nominal type system as well if we're talking about adding feature.

It's still a useful distinction IMO.

"Script" PLs tend to be interpreted, dynamic, and handwave various machine-level details. In contrast, "compiled" PLs usually provide you the constructs to manipulate native machine-level features directly.

Realistically, communities around "script" languages aren't going to talk much about memory layout or syscall. Instead, getting the job done fast (devtime-wise) is their main focus.

On the other hand, "compiled" languages tend to draw people who like squeezing every bit of computing power from their computer, even though it tends to raise the complexity.

C Is Best (2025) 7 months ago

FWIW, FAFO is a very good way to learn. Assuming we can respawn indefinitely and preserve knowledge between respawns, driving fast and taking off your seatbelt would definitely teach you more than just reading a book.

But in this specific case, if the respawn feature is not available or dying isn't a desirable event, FAFO might not be the best way to learn how to drive.

Well, in a language with nullable reference types, you could use something like

  fn find<T>(self: List<T>) -> (T, bool)
to express what you want.

But exactly like Go's error handling via (fake) unnamed tuple, it's very much error-prone (and return value might contain absurd values like `(someInstanceOfT, false)`). So yeah, I also prefer language w/ ADT which solves it via sum-type rather than being stuck with product-type forever.

Patterns.dev 7 months ago

I always wondered if people actually find it beneficial to follow these "design patterns" or not.

Personally, I prefer to learn FP patterns, which tend to be backed with nice mathematical properties behind them.

But in most cases you probably want something disjoint like Rust's `Result<T,E>`. In case of "it might be success with partial failure", you could go with unnamed tuples `(Option<T>,E)` or another approach.

That's needlessly aggressive. Ignoring webapps, you could do gamedev without even knowing what a matrix is.

You don't even need such construction in most native applications, embedded systems, and OS kernel development.

Has too much sugar, and without JetBrains IDE you're stuck with a plain text editor. Not sure if it's generalizable to normal Kotlin or not, but learning Gradle Kotlin DSL made me want to rip my hair out when trying to understand what happens under the hood.

I think it also depends on the community as well. Last time I touched Node.js and Javascript-related things, every time I tried to update something, it practically guaranteed something would explode for no reason.

While my recent legacy Java project migration from JDK 8 -> 21 & a ton of dependency upgrades has been a pretty smooth experience so far.

Like other sibling comment said, I think it's just your personal opinion. I don't think The Greedy Cave is that complex or belong to traditional roguelike category, more like MMO's grindy number game with some microtransaction.

  > I'd like to see a future where more developers have a basic understanding of ML even if they never go on to do much of it. I think we would all benefit from being a bit more ML-literate.
Why "ML-literate" specifically? Also, there are some people against calculus and statistic in CS curriculum because it's not "useful" or "practical", why does ML get special treatment here?

Plus, I don't think a "gotcha" question like "what is gradient descent" will give you a good signal about someone if it get popularized. It probably will lead to the present-day OOP cargo cult, where everyone just memorizes whatever their lecturer/bootcamp/etc and repeats it to you without actually understanding what it does, why it's the preferred method over other strategies, etc.

Where do you draw the line then? Stopping at `tau` just because `$future_maintainer` might get confused feels like an arbitrary limit to me.

What about something like `gamma`? Lorentz factor? Luminance multiplier? Factorial generalization?

Why not just use the full sentence rather than assign it to an arbitrary name/symbol `gamma` and leave it dependent on the context?

And it's not that hard to add an inline comment to dispel the confusion

  const tau = 2*pi; // Alternate name for 2pi is "tau"