Show HN: Wyzer Programming Language
github.com
after 5 months of research and a few weeks of development i am soon going to release version 0.1.0 of it, if you would like to contribute to it you're most welcome!
github.com
after 5 months of research and a few weeks of development i am soon going to release version 0.1.0 of it, if you would like to contribute to it you're most welcome!
However, your light is hidden under a basket, to use an old metaphor. I'm having to go digging to find the genuinely new things going on. I suggest recalibrating the entire documentation with a focus on the new stuff. People on HN often complain about not having syntax examples front and center but I would say for you, the very first thing I you should be hitting your new visitors with is the choreography idea.
Set up a simple example of doing something like a concurrent remote counter that is atomically safe by the construction of your language and immediately dive in to what that means. Forget even educating us on the rest of the mundane syntax of the language, immediately dive in to what that is and what that means. I see in the docs/ dir that it probably hurts your programmer mind to cover the choreography before covering sections 1-8, but you can safely assume that if you intrigue with the choreography that they'll hang around to learn about the rest, whereas you can't safely assume that a new reader will wade through all the rest of the relatively mundane details to get to the really interesting stuff.
A modern language with a modern take on compiling a "program" that takes a unified view of the world at the programming language level, and then emits a "server" and a "client" (and perhaps other roles) as separate executables is a pretty nifty idea. Hit it early and hit it hard.
We definitely fell into the author’s trap of structuring docs "bottom-up" (Prerequisites -> Basic Syntax -> Advanced Concepts) rather than leading with our actual core innovation: choreography.
We are restructuring the main README/docs front page right now to lead immediately with a concrete example of choreographic execution (e.g., atomic multi-node orchestration / client-server emitting) before getting into standard syntax.
Really appreciate you taking the time to dig into the docs/ dir to pull this out it’s a huge help for our presentation.
I mean, I'm phrasing that in marketing terms, but in this case it's in harmony with what your users want anyhow. We want to know ASAP why we should care about this language. So it works for everyone. Of course when it comes time to deliver the promise, normal programming language documentation is the way it is for a reason.
You should put these up front also.
This is not an LLM talking but a kid who is learning to write with them, if you look closely. Still concerning
Nothing worse than PDFs full of theory and no working examples of the thing you’ll actually be doing all day.
I get that it’s important to show what abstractions and methodologies can arise out of using it, but with a good example that would be obvious. It’s the developer community using the language that will come up with the best abstractions (libraries) that - if it has an active community - helps shape what those novel developer experiences are.
A good analogy is the development of the skateboard - released first as a scooter - until someone knocked the handle off a started “surfing” down the street. Just show me the thing. I’ll tell you if it’s cool and how to use it
The choreography stuff is actually really cool! Definitely agree this should be front and center
Should be the header.
Newer is not always better. Lisp is full of good ideas that haven't stopped being good, even if a pure Lisp experience is understandably not to everyone's liking.
The next thing is, I need more examples. Read me documents can scroll forever and that’s fine. Add examples for every concept your language wants to cover to it. This is your chance to think things through and make the read me and the language astonishing.
And your AI disclaimer (or your AI’s disclaimer) makes sense.
What stops a choreography where Claire send a message to Bob but Bob is waiting for Alice and Alice is waiting for Claire?
Is it like Rust memory safety where not all valid programs are accepted but all invalid programs are rejected?
I think some examples of the distributed code in action on a trivial and non trivial distributed example is more compelling than a 3d donut render
The protocol doesn't describe "Claire sends" and "Bob receives" as two independent actions that wait for each other, it describes them as a single communication in the global program/state.
When it gets executed into code, every send already has a corresponding receive by construction, so you can't write something like "Claire sends to Bob while Bob is actually waiting for Alice" unless the protocol itself allowed that execution.
So the cycle you're describing can't just accidentally appear because of sync issues, because the assumption is a communication is represented correctly at each turn.
So yeah it kinda does limit the set of all possible programs since I would expect not everything can be encoded this way also seems hard to resolve this in practice without only allowing communication b/w Wyzer systems.
I am not very familiar with this topic in practice so OP would be the best person to answer this, I am quite intrigued by how it works in practice as well.
A choreographic programming language features programming abstractions for programming communication intent. For example, often they have a primitive like:
Alice.expr -> Bob.x
read 'Alice communicates the evaluation of expr to Bob, which stores the message in its local variable x'. (In fact, we discovered that we can extend any mainstream language to have this kind of high-level primitives by extending data types with locations, see choral-lang.org).
This makes it impossible to write mismatched communication actions, because you're expressing both the send and receive actions in a single atomic instruction: they are well-matched by construction. You then build a compiler (typically called 'projection') that generates distributed programs for Alice and Bob -- the former doing the send to Bob and the latter doing the receive from Alice. We like making formal models of these compilers and mathematically proving them correct. A compiler that respects the choreography then automatically entails deadlock-freedom of the compiled code without the need for complex checks, because the source choreography cannot syntactically express deadlocked terms.
Consequently, there are no deadlocked distributed programs that we can compile from choreographies. It's an application of the neat trick of designing high-level languages for 'guiding' programming: instead of programming a distributed system with low-level primitives and then attempting the generally very hard task of checking for deadlocks, we use a high-level language where deadlocks cannot be written (or are at least easy to check against).
The above hopefully explains the intuition of how choreographic programming works. But then, as you did, one naturally asks: What can we express in choreographic programming languages? Are there fundamental limitations?
We do not know exactly yet; this is an area of very active exploration. Over the years, people have developed more and more clever choreographic programming languages that capture more and more interaction patterns.
What's perhaps surprising is that, for some theories of distributed languages (or interaction patterns, if you like), we know that choreographic programming is complete, in the sense that it can capture all deadlock-free systems that can be modelled in those theories. The first result of this kind was about capturing all interaction behaviours that can be described in linear logic (in the Curry-Howard interpretation of it with process calculi), but there are also works that can deal with recursive behaviour and even process spawning (fork). That's encouraging.
I think that investigating what the paradigm precisely can and cannot do is fascinating (but I'm very biased here..), not least because using a mathematically-modelled compiler lets us optimise the generated code aggressively (e.g., adding more asynchrony, as in Ozone). From the state of the art already out there, it looks like choreographic programming is 'expressive enough' for many different purposes. Hopefully it's gonna be the typical situation with high-level abstractions, whereby for most cases and most people the high-level language is gonna be good and low-level communication actions will be necessary only in niche scenarios. In the meantime, there are choreographic languages that can be integrated with middleware and foreign APIs to cover up for deficiencies (like Choral, HasChor, etc.).
Too bad the github readme doesn't explain how the choreographic side of Wyzer works, or even what it looks like (please correct me if I'm wrong, couldn't find it after a quick skim).
Where’s the cool stuff?
if you claim X and Y make us cool and different, then you should document X and Y in your readme. don’t tell me something is the star, and then hide it away. this is baffling to me.
In there it says you started this journey when you were 8 years old and that you are a 14 year old programmer
I'm not sure how I feel about this to be honest, I need to process that.
1. The v0id_isgood HN user impersonated the actual creator of Wyzer and posted this Show HN.
2. The v0id_isgood HN user then left the credentials to his HN account in the Wyzer discord.
3. Now the real creator of Wyzer is apparently using the v0id_isgood account to answer questions here?!
What's going on? This whole thing feels like a really strange hoax.
const MAX: u32 = 100; // Compile-time constant
let x: u32 = 10; // Cannot be changed
If x cannot be changed, how does it differ (meaningfully) from a compile time constant in a safe language, ie in a case of no raw pointers> 1. What Wyzer Is, In One Paragraph...
> 2. Why Make a New Language At All?...
Far too often I see projects on HN where, on a casual skim, I just can't figure out "the point." This is clear and concise.
---
One of the issues with "Writing one network rule that creates code for every computer. We borrowed this from academic research" is that often it's hard to have the same language on every computer. For example, for a web application, you're either going to have to transpile to Javascript or target WASM. (And WASM has a lot of overhead compared to Javascript.)
Definitely a cool idea. I guess time will tell whether lifting it into the language proves itself as worth it.
It does not. The term "garbage collectors" covers a whole spectrum of algorithms, some might slow you down (though not for the reason you may think) while others were invented to speed up memory management beyond that of C++, in exchange for other tradeoffs. Python's (mostly) refcounting GC is actually closer to C in its memory management overhead than to either Go or Java. It's also not what makes Python slow. Go uses a mark-and-sweep collector to find a balanace between speed, FFI, and footprint. Java uses moving collectors, which are faster - and some of which are even more predictable - than memory management in C++. That's because Java aims to offer better performance than C++ in large concurrent software, where low-level languages tend to suffer from various overheads due to their requirement for low-level control (Java trades off some performance in smaller programs, but mostly it trades of startup time and footprint). Moving collectors (but not refcoting collectors or mark-and-sweep collectors) are an optimisation over free-list approaches, not a compromise for convenience.
So it is true that slow programming languages tend to use some kind of GC, but that's not what makes them slow, nor does it make the super-fast languages that also use a GC (often of a very different kind) any slower. The range of languages that use GCs covers everything from the super slow to the super fast.
Are you arguing that GC is not inherently slower than other memory management strategies (e.g. the Rust approach)? Or just that the cost is not worth optimizing away?
1. Moving collectors have long been more efficient than malloc/free but only in the total work devoted to heap management, i.e. throughput. Moving collectors that also offer predictable low latency are much more recent. In fact, the first open-source, production-quality, high-throughput, low-latency moving collector is less than three years old [2].
2. Much more importantly - and this is something that we experienced low-level programmers have known forever but people without much experience in low-level programming seem to not know these days is that low-level languages are not optimised for maximum performance. They're designed for maximum low-level control. When programs are small, when they're not heavily concurrent, or when the hot path is relatively simple, this low-level control can, indeed, translate to very high performance. But when programs grow larger or more concurrent, low-level control can actually make some optimisations harder. In particular, moving pointers, which are required to enjoy the optimisation offered by moving collectors, is not compatible with the low-level control needed in low-level languages, and these languages prioritise low-level control over everything else, including performance (as low-level control is their primary purpose). And this is not the only example of optimisations that these languages make harder. This is why large and/or concurrent programs have largely migrated from C++ to Java and C# over the past few decades, and this trend isn't reversing. Again, for smaller and/or less concurrent programs, low-level languages still offer excellent performance in expert hands, provided you invest sufficient effort into manual optimisation.
3. The optimisation offered by moving collectors isn't free. Until recently, you had to pay in unpredictability and high latency (which is still the case in all languages except Java), and since the algorithm uses RAM to reduce the CPU needed for heap management, it does necessarily require higher footprint. You can enjoy a similar optimisation that turns RAM into free CPU in Zig by using arenas (this is harder to do in C++ and Rust). Zig's arenas are even more efficient than moving collectors, but they do require more effort, and they're less general.
[1]: https://www.cs.princeton.edu/techreports/1986/045.pdf Note that in practice, moving GCs are not quite as efficient as stack allocation (let alone more efficient), but heap allocations in Java are not as expensive as heap allocations in C/C++/Rust/Zig that utilise malloc/free. This is why in these languages we try to avoid heap allocations on the fast path.
Running a GC takes time, pollutes the cache, and is often run at an unpredictable time. Sure, the GC is not necessarily the SLOWEST thing about the language (python), but it's not helping, either.
Isn’t this only the case for tracing garbage collectors? (And even then, not all of them are stop-the-world.)
Yes, but for a moving collector that's less time than it takes to run malloc and free. The interaction of a moving collector with most object is bump allocation when they're allocated (similar to stack allocation) and... that's it. The GC never sees them again, scans them again, or is even aware of their existence (moving collectors don't have a free operation). Overall, moving collectors (but not other kinds of GC) reduce the work of memory management compared to malloc/free.
In low-level languages we try to avoid doing a lot of malloc/free not because heap memory management is slow in general, but because that approach to memory management is slow. Moving collectors are an optimisation designed to make heap memory management fast, but it requires that (nearly) all pointers be movable, something that low-level languages can't do because they have constraints that are more important to them than speed (you can't interact with the OS or hardware directly, i.e. without an FFI API, if your pointers are movable, and such direct interaction is the point of low-level languages).
That moving collectors (NOT the GC Python has; NOT the GC Go has) can, in principle, make heap memory management cheaper than stack allocation has been well known since the eighties. But until recently they had excellent throughput (somewhat similar to arenas) but potentially long pauses. It was only recently that they were made "pauseless".
> and is often run at an unpredictable time
How much work malloc and free need to do is also unpredictable, and a modern pauseless moving collector like ZGC spreads the work needed for memory management more evenly than malloc and free.
> Sure, the GC is not necessarily the SLOWEST thing about the language (python), but it's not helping, either.
There is very little resemblance between CPython's GC and Java. Python's memory management is closer to C's than to Java's. GCs cover such a wide spectrum of algorithms that it doesn't make sense to talk about them as a single category as far as performance tradeoffs are concerned.
I've written two separate moving collectors for dynamic language runtimes, as well as done significant work in realtime 3D graphics, and what you're saying mostly smells like bullshit.
> The interaction of a moving collector with most object is bump allocation when they're allocated (similar to stack allocation) and... that's it. The GC never sees them again, scans them again, or is even aware of their existence
I mean .. sure, but, a copying GC eats somewhere on the order of 10% of your total memory bandwidth just copying shit around. I can guarantee that if you use sane allocation strategies (arenas & freelists, pools, whatever) you spend <1% of your total system resources fucking around with memory allocation.
> Moving collectors [...] requires that (nearly) all pointers be movable, something that low-level languages can't do
Completely false. You have to do some manual bookkeeping in C++, Rust, Zig, whatever, but you can do it, and in fact many commercial GCs do (V8 is a good example).
...
The rest of what you said is just empty-sounding claims that I'm not going to address. I looked at the single paper that you linked in another comment, from the 80s, which is hardly relevant on modern hardware.
Please, if you're going to make the claims you're making, back them up with hard evidence. I've looked, and the overwhelming majority of papers out there claim that GCs are slow, memory hungry and, generally, a waste of time.
That's nice. I have ~25 years of experience with large C++ software, including hard and soft realtime systems, and I now work on the JVM.
> I mean .. sure, but, a copying GC eats somewhere on the order of 10% of your total memory bandwidth just copying shit around
Good moving collectors are designed to copy very little. That's the entire purpose of generations.
> I can guarantee that if you use sane allocation strategies (arenas & freelists, pools, whatever) you spend <1% of your total system resources fucking around with memory allocation.
In theory. When you're in charge of a >2 MLOC C++ system, maintained by a large team for well over a decade, you find that these optimisations are very costly. Plus, you commonly find large C++ software that needs sophisticated malloc/free allocators for acceptable performance (BTW, some of those allocators are almost the same size, in LOC, as that of ZGC, probably the world's most sophisticated moving collector).
That's why, where I used to work and oversee large projects, we migrated pretty much all systems (mostly soft-realtime defence software) to Java from C++ - for better performance. One of the goals of the JVM was to tackle the familiar performance issues that plague large C++ programs.
And BTW, using arenas isn't so easy when programs get large and sprawling, or even in general. Zig definitely makes that much easier, though.
> Completely false. You have to do some manual bookkeeping in C++, Rust, Zig, whatever, but you can do it, and in fact many commercial GCs do (V8 is a good example).
What I said is completely true, but you may have misunderstood it. You can, of course, combine moving collectors with things that expect stable ones in the same process. In fact, you absolutely must, because at some point in the stack you need to talk to the OS and/or hardware, and they expect stable pointers. But you have to have some distinct FFI layer between the two, and the entire point of low-level languages is to be at the lower level.
> Please, if you're going to make the claims you're making, back them up with hard evidence. I've looked, and the overwhelming majority of papers out there claim that GCs are slow, memory hungry and, generally, a waste of time.
I don't know what you've read, but that is very clearly not the consensus among memory management experts. My "claims" are pretty common industry knowledge, and why the majority of performance-critical large software has migrated away from low-level languages over the past couple of decades, and the trend continues. I'm not trying to change anything, I'm just explaining why the industry is doing what it's doing to those who may not be familiar with large, long-lived software.
It's funny, but 25 years ago, the people who doubted the amazing performance-per-effort of moving collectors and JITs were those who (like me) had not used those technologies and were mostly familiar with low-level languages. These days, it's the people who have little experience developing and evolving large and complex software in low-level languages (TBF, there's much less such software written in low-level languages these days) that believe the low-level languages are inherently fast.
Having said all that, when programs are relatively small and/or not very concurrent, the effort required to match or beat Java's performance in a low-level language through careful manual optimisation is sometimes worth it. In large software, it gets harder and harder.
> I can guarantee that if you use sane allocation strategies (arenas & freelists, pools, whatever) you spend <1% of your total system resources fucking around with memory allocation.
I just spoke with someone on the performance team at one of the world's largest tech companies who told me that some of their larger Rust programs spend 30% of their CPU on malloc/free. Of course, it's possible in principle to reduce this given enough effort, but this is identical to the experience we've had with C++ for decades: When programs are small, it's easy to get good performance, but as they get larger, the areas where low-level languages have intrinsic inefficiencies (such as dynamic memory management) tend to become more pronounced in practice and the programs are not so easy to optimise.
There's no need to get upset. The person you're replying to is a language expert; they're not tearing you down.
GCs have actually advanced quite a bit in the last decade, though they may remain a bad fit for the usecases your career has focused on.
ps: were you looking at efforts such as clojure electric (https://github.com/hyperfiddle/electric) ? they aim to represent computation on different hosts as one expression.
What happens if the piece of data has multiple owners? Do you get sudden performance bugs which are hard to debug?
Most of the important stuff is in this section:
```3. What's Actually New About It?```
But you don't really go into any details about what's actually happening, have any honest conversation about any trade-offs, mention anything about why this is memory safe & correct now (highly skeptical), or why you have a clear path towards that in the near future.
From the VERY brief examples you show, it looks like it's just Rust without a borrow checker. The borrow checker gives Rust a bad reputation, but it's only a very small part of why Rust is hard. You didn't give any insight into how you're solving the rest of the problems.
Is choreographic programming the same as session types?
Is mut a thing? looks like rust.
But .. your language mostly hide when you're doing an 'inside' function call or an 'external' function call. I'm not sure I like this; 1) the latency of both operation is very different so you want to minimise the number of 'external' function calls 2) what happens in case of 'timeout' for external function calls? I didn't see it in the doc, did I miss it?
Can you you give a hello world example in each of them and show where your languages shines?
Promising insofar as all of the essentials seem to be right (for me): compiled, good type checker, no garbage checker etc.
https://felix-tutorial.readthedocs.io/en/latest/intro_corout...
Note this is for cooperative threading.
> The one rule is that once you use a resource, you cannot use it again
What about data structures which need to be shared? Can I use some sort of mutex for them?
It's "pretentious."
(said pretentiously, lol)
Hey, at least we know AI didn't write it, lol
https://www.microsoft.com/en-us/research/wp-content/uploads/...
(wait what is that WordPress?)
Also I really like that somebody said that Rust syntax is complex and too hard to learn. So many unreadble things. However Rust did good think about fn keyword - it fits better than fun, func, function keywords. I hope you will not choose path where every 2nd word is abbreviated.
Made an error? Hold your beer, Bud's got your back: the Bud Wyzer compiler!
The resource rule as stated is linear: once you use a resource, you can't use it again. Perceus is not that. Perceus exists precisely because values are shared — it inserts dup/drop and then reuses the allocation in place when the count happens to be 1. If everything in the language were genuinely use-once, you wouldn't need refcounting at all; a linear type system gives you the frees statically. The fact that Perceus is in the design implies aliasing is allowed, which means "one owner" is a description of the socket/interrupt layer, not of memory.
That's fine as a design — but then the elevator pitch is "two rules that rhyme," and the FAQ's central claim ("you only need to learn one rule") is the thing I'd expect to break first under contact with real programs.
Related, and more concrete: Koka and Lean get away with RC partly because their data is overwhelmingly acyclic by construction. Your README shows var bindings and mutable struct fields. Mutation plus refcounting gives you cycles, and cycles leak. What's the plan — a cycle collector, weak references, a type-level acyclicity restriction, or accepting the leak? DESIGN.md would be a good place to state it outright, because it's the first question anyone with RC experience will ask.
This is just slop
But why!? You know what'd be cool? If we started supporting control statements that were a bit more sophisticated!
``` do { } while (c) { if (x) break foo; } else { case foo : ...; default : ...; } ```
asking out of my own ignorance, what's so hard with rust that you cannot convince people and contribute to that directly instead of going on your way? i remember so few of these projects survived over the years. do you think it'll really become something other than your pet project?
It takes the fun out of this. I like JavaScript and Python. If I need to type system, there’s always type script and C#.
I don’t care about a bunch of memory management details, and if I ever want to I might as well use C++ and become a quant.
I learn a new programming language for one of two reasons, there’s a framework or engine that requires it. For example, Godot with GD script.
Or to make more money.
Python was a very happy mix of both, I learned it to work with ML libraries as a hobby and later found it pays a lot more.
Are we just straight up inventing buzzwords now?
Hey guess what, I'm more into symphonic opus programming, none of that backyard dance-club programming for me thanks.
/s [no hate, just laffs]
Nothing like establishing a new buzzword in a field to get them citations rolling.