Live data from Hacker News

A few good ideas in programming languages

prydt.xyz

61–70 of 77 posts

Re: A few good ideas in programming languages

#61
post #34

I didn't expect this to get posted here. Long time lurker here. I'm really interested in programming language design and ergonomics. What niche PL features would you like to see have more adoption?

Generic narrowing types / linear types (like if you check that a string has length 10, then its type knows, and functions accepting bounded strings can accept it.)

This makes it easier to split raw inputs from validated inputs and delimiting where they are used in the code.

Re: A few good ideas in programming languages

#62

Earlier quoted context omitted.

Rust already supports the kind of behaviour you are describing for borrows, because of non-lexical lifetimes. Code like the following now compiles: fn main() { let mut x = 42; let y = &x; println!("{y}"); let z = &mut x; } Even though y's scope overlaps with z's, and they introduce conflicting borrows, this code compiles because the compiler treats y's borrow as dead after its last use (this has been true since Rust…

> If you didn't have that guarantee, at worst your mutex's guard object would be immediately dropped For named local variables it's a different story. They should remain alive until the end of their lexical scope. But for unnamed temporaries created in expressions different rules should apply - as soon as there is no reference to such temporary, it should be destroyed.

Okay, I see. The issue you are running into is specifically mentioned in this article about how Rust currently does lifetime extension:

https://smallcultfollowing.com/babysteps/blog/2023/03/15/tem...

Typically, a temporary's lifetime is bounded by the statement it is in, but for the subject of a match, this extension overlaps with all its arms even if the temporary borrow is not used after the subject is evaluated (i.e. you borrowed, you read and copied a field out of the borrow).

The issue seems to be that this is a syntactic transformation, but the expected behaviour requires type information, so you can tell whether to extend the temporary's lifetime by whether that lifetime leaks the immediately containing scope.

This is kind of similar to how type parameter unification in Hindley-Milner works. There's even an analogy made between the two things here:

https://okmij.org/ftp/ML/generalization.html#gen-mismanageme...

Re: A few good ideas in programming languages

#63
post #10

Earlier quoted context omitted.

Poor man's runtime "dynamic" version. AKA: A much worse version. In advanced cases, you'd need dependent types, but the only place where that almost shows up is in the "amount "Contracts" has been around a long time and has not caught on. That's usually a good sign that better approaches are prevailing. In other words: refinement types are a better solution.

> Poor man's runtime "dynamic" version. AKA: A much worse version. Contracts don't have to be evaluated dynamically, that's just one way they're implemented. See SPARK/Ada for an example of contracts being used to prove programs statically, not just test them dynamically.

My rcc C compiler has a compile-time contracts and range/interval prover also. Needs -O3.

For full formal proofs it's easier to use cbmc or esbmc though

Re: A few good ideas in programming languages

#64
Could someone explain the appeal of flow typing?

I can see how it can be useful to start with a broad type, e.g. a union, and narrow it down in a block. However, I don't quite get the opposite direction shown in their example (first an int, then a string, then a union).

Re: A few good ideas in programming languages

#65
So while the ideas discussed are interesting, the origins are a bit off.

Flow typing, is actually called Flow-sensitive typing.

Contracts were introduced into the industry via Eiffel, which continues to be sold via Eiffel Software company.

By the way, at the recent DConf 2026, during the panel discussion, contracts was actually one of the features that were discussed as something that they would remove from the language, if doing it all over again.

Rust's borrow checker, is based on Affine Types, and the first systems language that looked into it was Cyclone, which AT&T started as research project in colaboration with an university, to eventually replace C.

Re: A few good ideas in programming languages

#66

For pedantry, should we note that design by contract came all the way from Eiffel ? (But it's possible that even less people ever wrote Eiffel than D, so, who knows)

I (briefly) used Eiffel in the 1990s. It had some of, if not the, worst tooling I've ever experienced for a programming language, and I've used COBOL compilers and MVS. A pretty nice language, but the software tool support initially was appalling.

Re: A few good ideas in programming languages

#67

Earlier quoted context omitted.

You can very likely borrow check in languages that don't have it in the type system. Exactly the way you suggest, as an optional add-in. It's still WIP but in my side project I haven't found cases that can't be handled yet. https://github.com/ityonemo/clr

> Exactly the way you suggest, as an optional add-in No, I don't suggest it, but criticize it. Rust performs its checking as a separate step after actual compilation, which sometimes leads to strange behavior (like borrow errors are shown only after actual compilation errors). I prefer an approach which is integrated with other language mechanisms. > It's still WIP but in my side project I haven't found cases that ca…

> but I doubt it can be useful without proper integration with the language itself

So projects like mypy or sorbet aren't useful because they aren't properly integrated?

Re: A few good ideas in programming languages

#68
post #43

Earlier quoted context omitted.

My idea was that with single pass, I can build SSA form during AST construction, and use phi-nodes to update type flow info. Then I could use SSA form to prove that I can use certain optimized bytecode instructions when a variable/register is known to be of certain type (I have virtual registers and fat instructions, eg ADD takes 2 sources and destination). Maybe I'm mixing control flow, type flow and SSA. I do not u…

Yep, this sounds like conflating two different ideas about SSA. You could parse a source language with shadowed variables into an AST, and then one of your earliest AST transforms could be a 'de-shadowing' pass. The resulting AST would only see variables assigned only once. Then a type-inference pass, where your AST expressions would gain type info. (Then a bunch more passes, e.g. closure conversion if you have them)…

Shadowing at AST level with the lexical scope is easy to implement, it's just each usage looks up inside out to parent scopes. But if we treat each assignment as a kind of shadowing, it works in a similar way and turns into a kind of SSA. The complexity arises with phi-nodes when multiple paths join. I think the confusion comes from the strict definition of SSA as something useful for the very late stage in the pipeline, but the same concept can exist much earlier in the pipeline.

Re: A few good ideas in programming languages

#69

Nice list. I have a new language I'm working on (called Zena: https://zena-lang.dev/ ) with all of these in some form: If you have static types and unions, control-flow analysis and narrowing is critical for avoiding an excessive amount of casts - and if you also have pattern matching, you get very nice style where a type-check, state extraction, and branch are all one expression. Borrow checking. Zena is a GC'ed lan…

Zena looks super cool! I was wondering if you could walk me through this syntax thats part of the example loops: ``` let iterator = items.[Iterable.iterator](); // Dimensional types and formal verification make me super excited to see more of this language. You also probably mention this somewhere and I'm missing it, but any thoughts on adding pure functions / more general mutability enforcements?

Thanks!

So `items.[Iterable.iterator]()` is invoking a symbol-keyed method.

It's declared like:

    export interface Iterable {
      static symbol iterator;

      [iterator](): Iterator;
    }

    export MyArray implements Iterable {
      [Iterable.iterator]() { ... }
    }
This is similar to JS, where you can access properties of an object dynamically with [] notation, but Zena is static and doesn't have any reflection (yet) so the symbol has to be declared and statically resolvable, and Zena has operator overloading an a [] operator so we need a way to differentiate between symbol-keyed access from indexed access ([]), thus the o.[] syntax.

I do want to add pure functions, especially for compile time constants. I want to add a macro system that can either run pure functions (on the AST or IR, not sure yet) at compile time, or run arbitrary code sandboxed in a Wasm module.

Re: A few good ideas in programming languages

#70

For pedantry, should we note that design by contract came all the way from Eiffel ? (But it's possible that even less people ever wrote Eiffel than D, so, who knows)

I (briefly) used Eiffel in the 1990s. It had some of, if not the, worst tooling I've ever experienced for a programming language, and I've used COBOL compilers and MVS. A pretty nice language, but the software tool support initially was appalling.

That was due largely to the designers of the language, Bertrand Meyer and his wife, creating the tools. The tools, the seminars, and the books were primary income streams.
Post reply on HN