5 ms·
I'm building a clarity-first language (compiles to C++)
- dusanstanojevic 8mo agoVery interesting, I've read your readme and your core principles really resonate with me. How is memory managed?
- hedayet 8mo agoGreat question - we keep memory management intentionally simple. 1. There’s no manual memory management exposed at the language level (no pointers, no allocation APIs). I intend to keep it this way as long as possible. 2. Containers (list[T], dictionary[K,V]) compile directly to C++ STL types (std::vector, std::unordered_map). 3. Values follow a strict rule: primitives pass by value, containers pass by read-only reference. This prevents accidental aliasing/mutation across scopes and keeps ownership implicit but predictable. Anything created in a scope is destroyed when that scope ends (standard C++ RAII). So in practice, memory management in Rox is C++ lifetime semantics underneath, but with a stricter surface language to reduce accidental complexity.
- teraflop 8mo agoThat sounds like it's basically impossible to implement your own non-trivial data structures. You can only use the ones that are already in the standard library. For instance, how would you represent a binary tree? What would the type of a node be? How would I write an "insert node" function, which requires that the newly-created node continues to exist after the function returns? I'm not necessarily saying that this makes your language bad, but it seems to me that the scope of things that can be implemented is much much smaller than C++.
- sesm 8mo ago> Lists are accessed only via .at() If clarity is the goal, then data structures that support access by index should be called `arrays` or `vectors`
- rkeene2 8mo agoAlso why num/num32 for Integer types, and no floating point type
- hedayet 8mo ago[author here] Very good questions; I definitely would like to revisit num32 very shortly. I'd say the initial rational of having num32 is not coherent right now, but I'll have to verify removing the support. we have floating point type(It was missing from the type list in readme. I have just updated that seeing this comment. thank you!)
- teiferer 8mo agoWell, clarity would be achieved with a name like u64. Is num signed? What's the range? Is it integers or floating point? All these things are hidden. With u64 there would be no questions open. (Well a few maybe, like overflow behavior, but can't have it all..)
- rurban 8mo agoThere cannot be any num32. num is a number, which can be fixed size integers, floating point numbers (of fixed size or not) or bigints. Some also add decimals num32 being i32 or f32 makes no sense
- hedayet 8mo agonow we have only int64 for integer types and float64 for floating point types. Thanks everyone for your thoughtful feedback!
- rkeene2 8mo agoNeat !
- joshuamorton 8mo agoThis is very language dependent. People coming from python or Java would call them lists. Vectors are a mathematical concept unless you use c++.
- amluto 8mo ago> 3. Values follow a strict rule: primitives pass by value, containers pass by read-only reference. This prevents accidental aliasing/mutation across scopes and keeps ownership implicit but predictable. There are plenty of languages where functions cannot mutate their parameters or anything their parameters reference — Haskell is one example. But these languages tend to have the ability to (reasonably) efficiently make copies of most of a data structure so that you can, for example, take a list as a parameter and return that list with one element changed. These are called persistent data structures. Are you planning to add this as a first-class feature? This might be complex to implement efficiently on top of C++’s object model — there’s usually a very specialized GC involved.
- eager_learner 8mo agoComments like amluto's above, are the reason my time spent on HN is not wasted.
- paulddraper 8mo agoI don’t see the relevance of special GC. But yes you need immutable data structures designed for amortized efficient copy.
- cyber_kinetist 8mo agoI've seen some C++ libraries that implement persistent data structures like immer (https://github.com/arximboldi/immer https://github.com/arximboldi/immer) - but seems it requires the use of the Boehm GC (which is notorious to be slow, since it is a conservative GC and cannot exploit any of the specific semantics/runtime characteristics of the language you're making).
- hedayet 8mo ago[author here] ROX avoids implicit structural sharing and persistent data structures. Allocation and mutation are explicit - if I want a modified container, I construct one. This is intentionally more resource-intensive. ROX trades some efficiency for simplicity and predictability. The goal is clarity of logic and clarity of behavior, even at slightly higher cost. And future optimizations should preserve that model rather than hide it.
- OsrsNeedsf2P 8mo agoThis is great. I look forwards to more "strict" languages whose deterministic compilers will give LLMs a tight feedback loop to catch bugs.
- Panzerschrek 8mo agoDoes it have destructors?
- hedayet 8mo ago[author here] as of today - no. I'm super keen to keep the concept of destructors and GC hidden from this language interface.
- deleted 8mo ago[deleted]
- leecommamichael 8mo agoI’d be curious to hear the author’s thoughts on Odin. Odin seems to have meet many of the same goals as ROX. I am not implying the author shouldn’t keep going with their language.
- nynx 8mo agoThis is an interesting line in the readme: > The language forces clarity — not ceremony. I find this statement curious because a language, like this, without ability to build abstractions forces exactly the opposite.
- hedayet 8mo ago[author here] That’s a very good point - "not ceremony" was poorly phrased. ROX does introduce more explicitness, which indeed introduces more ceremony. The goal isn’t to reduce keystrokes; it’s to reduce hidden behaviour. A better framing would be: ROX prioritizes clarity over convenience. Explicitness may cost more keystrokes, but it eliminates hidden behavior. [README updated]
- saghm 8mo agoYeah, this seems to be a common thing nowadays, although often with the value cited as "simplicity". I've always found it a bit odd because it seems to me like there are tradeoffs where making things at one level of granularity more clear or simple (or whatever you want to call it) will come at the cost of making things less clear and simple if you zoom in or out a bit at what the code is doing. Assembly is more "clear" in terms of what the processor is doing, but it makes the overall control flow and logic of a program less clear than a higher level language. Explicitly defining when memory is allocated and freed makes the performance characteristics of a program more clear, but it's "ceremony" compared to a garbage collected language that doesn't require manually handling that by default. I think my fundamental issue with this sort of prioritization is that I think that there's a lot of value in being able to jump between different mental models of a program, and whether something is clear or absolutely ridden with "ceremony" can be drastically different depending on those models. By optimizing for exactly one model, you're making programs written in that language harder to think about in pretty much every other model while quickly hitting diminishing returns on how useful it is to try to make that one level of granularity even more clear. This is especially problematic when trying to debug or optimize programs after the initial work to write them is complete; having it be super clear what each individual line of code is doing isolation might not be enough to help me ensure that my overall architecture isn't flawed, and similarly having a bunch of great high-level abstractions won't necessarily help me notice bugs that can live entirely in one line of code. I don't think these are specific use cases that a language can just consider to be outside of the scope in the same way they might choose not to support systems programming or DSLs or whatever; programmers need to be able to translate the ideas of how the program works into code and then diff between them to identify issues at both a macro and micro level regardless of what types of programs they're working on.
- tovej 8mo agoYou have to unwrap every array access? That does not feel clear to me at all. Also this would make every hot loop slower. The amount of safety features here seems excessive. The language is stricter than Rust. It's not very "clear" either. For some reason the author has decided to rename concepts that are familiar to programmers, making it more didficult to switch to for experienced programmers (repeat instead of for, num instead of... float I assume?), but the langauge isn't really beginner-friendly either, due to the strict semantics. This feels like vibe-coded slop. Why is this on the front page? HN has fallen off.
- anon291 8mo agoThe best imperative language is Haskell do notation which offers everything you support here.
- Knork-and-Fife 8mo agoIf the only loop is `repeat i in range(start, end, step)` , how do you do a loop like "Keep reading from a buffer until it's empty"? I.e. any loop when you can't know the number of iterations needed when the loop starts?
- HendrikHensen 8mo agoIf this is to be a real, (relatively) widely-used language, I would make some tough choices on where to innovate, and where to just leave things the same. One thing I noticed in the example is `num target`, especially because the focus is on "clarity". When I read the example, I was sure that `num` would be something like the JavaScript `Number` type. But to my surprise, it's just a 64-bit integer. For an extremely long time, languages have had "int", "integer", "int64", and similar. If you aim for clarity, I would strongly advise to just keep those names and don't try to invent new words for them just because. Both because of familiarity (most programmers coming to your language will already be familiar other languages which have "int(eger)"), and because of clarity ("int(eger)" is unambiguous, it is a well defined term to mean a round number; "num" is ambiguous and "number" can mean any type of number, e.g. integer, decimal, imaginary, complex, etc). The most clear are when the data types are fully explicit, eg. `int64` (signed), `uint64` (unsigned), `int32`, etc.
- hedayet 8mo ago[author here] That’s a good point. I can see why int might be clearer than num, especially given the long history of that naming. I’ll think about it.
- Nevermark 8mo agoDefinitely int for signed numbers. But I would call it "int64". Clarity means saying what you mean. The typename int64 could not be clearer that you are getting 64 bits. This is consistent with your (num32 -->) "int32". And it would remain consistent if you later add smaller or larger integers. This also fits your philosophy of letting the developer decide and getting out of their way. I.e. don't use naming to somehow shoehorn in the "standard" int size. Even if you would often be right. Make/let the developer make a conscious decision. Later, "int" could be a big integer, with no bit limit. Or the name will be available for someone else to create that. I do like your approach. (For unsigned, I would call them "nat32", "nat64", if you ever go there. I.e. unsigned int is actually an oxymoron. A sign is what defines an integer. Natural numbers are the unsigned ones. This would be a case of using the standard math term for its standard meaning, instead of the odd historical accident found in C. Math is more universal, has more lasting and careful terminology - befitting universal clarity. I am not a fan of new names for things for specialized contexts. It just adds confusion or distance between branches of knowledge for no reason. Just a thought.)
- ad_hockey 8mo agoThis looks interesting! As a Go user I definitely see the value in boring but predictable languages. Does Rox have any support for concurrency?
- skottenborg 8mo agoThough it may be somewhat annoying, I think forcing named parameters would be a big win for clarity. Is this something you have considered?
- globalnode 8mo agoone thing ive always wondered about these type of projects is how do you debug them? gdb at runtime? printf statements? i mean when im debugging python i mainly rely on print() and log files so i guess that would work. its been a long time since i used an ide to step through a program anyway, i think way back with borland turbo c/c++ i used to step through statements to see how it all worked but things are much too complex for that now.
- ivanjermakov 8mo ago> In ROX: <...> Nothing implicit happens behind your back. > You write the logic. The language stays out of the way. Writing business logic and everything being explicit are polar opposite. For the programming language to stay out of the way it should more resemble concise version on English with little to no language constructs.
- bsaul 8mo agosidenote : are we going to still see new languages appear after AI becomes the one that writes the code ? I'd say that for a new language to appear in that new world, it would need to offer new compile-time properties that AI could benefit from. Something like expressing general program properties / invariants that the compiler could check and the AI could iterate on.
- norman784 8mo agoMost of what the section "Why ROX exists" reminds me of Rust and Zig, where both are more explicit (but Zig even more where there aren't hidden allocations, while Rust hides it). Said that I really miss all the i{8|16|32|64|128|size}, u{8|16|32|64|128|size} and f{32|64} in other languages, I think having num and num32 is a mistake (IMHO) and naming like them like Rust/Zig provides more clarity (and it's also more concise). For the "repeat" keyword I find it odd, because most other languages uses "for" but I can understand the reason and might be able to get used to it. Otherwise I find always interesting new programming languages, what new they bring to the table and the lessons learned from other PLs.
- Someone 8mo ago> I think having num and num32 is a mistake There still are lots of 32-, 16- and even 8-bits CPUs in active use. For those portable code that uses machine integers can make sense (and yes, it is harder to write because the integer limits will vary with the integer type used, but the added performance can be worth that).
- Someone 8mo ago> Nothing implicit happens behind your back. And then, we have repeat i in range(0, n, 1) with an implicit “i = i + n”, followed by an implicit comparison. Also, range(0, n, 1) looks like a function call, but isn’t one, is it? Or an i write evenDigits = range(0, 10, 2) I think introducing a range type would be cleanest, but I think clarity would improve even more by giving up more on “Nothing implicit happens behind your back”. Allowing for num in nums {…} has quite a bit of magic, but IMO is clearer than iterating over indexes, followed by superfluous checks whether indexing succeeded.
- repstosb 8mo agoIf the goal is truly "clarity", then I fail to see how this leads to more readable programs than Knuth's Web (or cweb for a more practical implementation). If you really mean "C/C++, but with the sharp pointy bits filed down," then I fail to see what it adds over MISRA. The bottom line is that we've had "clarity-first" languages for decades; the reason they're not more widely used is not simply that no one has tried this, nor that programmers appreciate murky code.