
Show HN: Muon, a low-level programming language inspired by C, C# and Go - nickmqb
https://github.com/nickmqb/muon
======
tagrun
I have a criticism regarding how basic data types are named:
[https://github.com/nickmqb/muon/blob/master/docs/muon_by_exa...](https://github.com/nickmqb/muon/blob/master/docs/muon_by_example.md#core-
types)

Type names like long, short, char (and the oh-so-dear long long) are ancient
cruft which shouldn't be used in a new programming language. They are
confusing (as in, the answer to question: how many bits are there in your int,
long, short depends on both arch and compiler) and since 70s we discovered it
the hard way that they're non-extensible (multiple times, actually!). Similar
for the fate of char in the post-ASCII world. C still keeps them for backward
compatibility, but types like uint32_t defined in stdint.h is becoming common
practice for new code.

The choices are also not compatible with C or Go, adding further to the
confusion (Muon's int is 32-bit and long is 64-bit on all archs!), so even for
C/C++ and Go programmers, the nostalgia turns into a pitfall. Having no
familiarity with C# (not much of a Windows user since late-90s, and C#
virtually doesn't exist for Linux/macOS people which Muon claims to be
targeting), I looked it up, and it seems it's based on C#'s distorted and
incompatible adoption of C89's data types.

Go applies the same custom to float/double as float32/float64 which makes it
uniform. Go also defines int to be the size of the native integer register
size on the target arch (which I like because this is what int supposed to
mean originally, this was the common understanding when porting between C and
assembly, although some may disagree after ~50 years of weird adaptations of
it) which unifies uint and size_t, rendering size_t obsolete.

Ironically, Muon makes use of explicit number-of-bits suffix for one data
type: bool32 (and there's no bool16 or bool64). I don't even know why one
might have it as a basic language type.

I really hope he adopts a naming convention that is similar to C99's stdint.h
or Go (which the author claims to be inspired by), or their shorter cousins
u8, u16, ... used in Linux source code.

~~~
nickmqb
Yup, the type names for integers (byte, short, int, long, etc.) are borrowed
from C#. All primitives in Muon have a well-defined, platform/architecture
independent size. Two exceptions: ssize and usize (machine word sized
integers), which are mostly used for C interop.

C# programmers should feel right at home, but you're right that there is a
very slight learning curve for others.

But, I hear you! I've personally worked a lot with C# which is why I chose the
current names, but I realize that there are others that prefer the explicit
names (u8, i8, etc.). I'll be adding a type aliasing feature soon, so anyone
who wants to define their own alternative type names will be free to do so. At
that point it may also make sense to use explicit names as the default.

~~~
zeotroph
Related, and very much a personal preference: Given that there will be no
classes etc, isn't the use of camel case (and the not-that-different Pascal
Case) for both structs and functions giving up a dimension of distinction?

The ClassCamel and snake_function_case split of Python and Rust is something I
bikeshed about often - sooner or later 'IO', 'TCP' or 'MMbT' needs to be
CamelCased and then it all goes downhill. Or C and C++ (the latter is also
mentioned as an inspiration), which never used CamelCase. I think Java then
used it purely to distinguish itself also by style from these direct and
unsafe memory poking languages.

~~~
cpeterso
I'm surprised that Ada's Camel_Snake_Case isn't more common. It's no more
verbose than snake_case but easier to read the words like a natural language
and avoids acronym capitalization ambiguities, e.g. "XML_HTTP_Request" instead
of "XMLHttpRequest".

~~~
int_19h
I think it's uncommon because it is redundant - if you already have
underscores to separate words, why capitalize?

Personally, though, I find casing to be a better way to split up words in
languages which have . as a member access operator, for the simple reason that
it makes property or method call chains more readable. When you have both dots
and underscores in the mix, they both register up as whitespace first, and
then you have to distinguish which is which. When it's just dots, it's clear
where the boundaries are.

------
austincheney
This language looks incredible. I like the code examples and was really sold
when I read its point number 2 about functions and no inheritance.

The only frustration I immediately notice is strong reliance on white space
for syntax.

* This makes life harder in a modern world of various operating systems and high distribution. Some means of distribution mutilate white space. XML language design spent enormous effort considering for this.

* Line termination differs by OS. Now it’s largely just posix vs Windows, but there used to be even more variance and there could be more in the future.

* Parsing around white space is severely misunderstood by people who do not write parsers regularly. Just this weekend I encountered unique white space syntax added into React’s JSX to compensate for this that doesn’t exist anywhere else in either JS or XML.

* As somebody who maintains a popular code beautifier I have had to learn the hard way that people really enjoy flexibility around white space. They deliberately use my software instead of, or in addition to, the popular software coming out of Facebook because of the flexibility my software allows. The bottom line for many developers is that code beautification can be fully automated and is better performeded by computers and should never be a factor when authoring code.

* code beautification is also highly subjective. Everybody has different opinions on beauty and easy to read. Providing the means to allow developers to exercise those opinions dynamically and automatically makes people happy. So long as it’s just vanity the code stills executed and reads the same way.

~~~
fnord123
>* code beautification is also highly subjective. Everybody has different
opinions on beauty and easy to read. Providing the means to allow developers
to exercise those opinions dynamically and automatically makes people happy.
So long as it’s just vanity the code stills executed and reads the same way.

No. Formatters have won. All code should be run through a formatter that
contains zero options. The result is the how the code looks. Obviously
languages with significant whitespace are more limited in what their
formatters can do, but the point about it being subjective is irrelevant.

~~~
bauerd
You forgot to mention why formatters would be superior. I do share your
opinion, and the two reasons I can think of are (1) consistency and (2) short-
circuits style discussions in code review.

------
jph
I like the emphasis on simplicity. I like the functions, the namespaces, the
customizability of allocators, and more. Much respect to the author for
creating Muon and sharing it.

What sets Muon apart from all other languages that I've personally tried is
that Muon seems to have chosen a unique indentation approach. First, there is
a double-syntax for blocks, because Muon requires block indents with
significant whitespace (akin to Python) and simultaneously requires block
open/close braces (akin to C). Second there is a free choice of tabs or spaces
(but not both) and simultaneously a meta-markup line that a developer can put
at the top of a file that enables mixing tabs and spaces.

IMHO these kind of whitespace choices seem small at first, yet eventually make
open source development more difficult when teams make different choices, and
when teaching examples make different choices. I'm a big proponent of
`rustfmt`, `gofmt`, and similar tooling that makes it easy to align diverse
teams from many groups.

~~~
nickmqb
That's a fair point. It's the reason that I've marked significant whitespace
as "experimental" in the documentation. Since the language is in an early
stage, I think it's OK to try some slightly more experimental constructs for
now. The feature does provide some nice benefits in terms of parsing
robustness, which is really noticeable especially when working with live error
feedback (i.e. language server, coming soon).

But rest assured, this is very much on my radar! If it turns out to be too
much of a pain, I'll remove it (or make it opt in). Fortunately, because of
the redundant braces the transition would be seamless: we can just drop the
whitespace requirement.

~~~
ahaferburg
I actually think that this is pretty close to the sweet spot. You make line
breaks significant, but not the indentation level. The indentation will be
inferred automatically from the braces. That way you can paste code that has
the wrong indentation, hit save, and as long as the braces are balanced, the
indentation will get fixed automatically by an auto-formatter.

I would be very interested to know how happy Python programmers are with the
indentation level being significant. Did Guido ever say anything about it?

~~~
lgas
I worked almost exclusively in python for about five years and loved
significant whitespace. It was one of the things I missed most when working in
other languages. Now I work primarily in Haskell which has significant
whitespace with optional braces and I still love significant whitespace and
never use the brace style.

In practice I found the copy/paste problem exceedingly rare to the point that
it has been completely a non-issue in both languages.

------
saagarjha
More interesting link, I think:
[https://github.com/nickmqb/muon/blob/master/docs/muon_by_exa...](https://github.com/nickmqb/muon/blob/master/docs/muon_by_example.md)

Uniform call syntax and constructor functions go a long way in making usage
more pleasant. I’m curious about “tagged pointers”, though: is the only way to
make a “union” by taking the reference of a bunch of things? Seems somewhat
inconvenient. Finally, there seem to be a couple of opinionated requirements
in the language, namely significant whitespace (made “worse” because you’re
already using brackets to define scope) and restricting generic parameters to
single-letter names that seem like they’re a bit _too_ restrictive.

~~~
nickmqb
Correct, tagged pointers are just for pointer unions, a value union could be
emulated using the transmute builtin function. A more general mechanism for
discriminated unions is on the roadmap. It would have been nice to include it
for the initial version, but I've been in stealth mode for too long already so
really needed to launch ;).

Re: whitespace, see my comment here:
[https://news.ycombinator.com/item?id=19599908](https://news.ycombinator.com/item?id=19599908)

I'm not worried about single letter generic param names being too restrictive.
Having more than 2 or 3 type params usually implies some fairly heavy
abstraction, which is not really in the spirit of the language.

~~~
saagarjha
My concern with generic parameters was that I might have situations where both
of them start with the same letter so I have to assign an unrelated letter to
one of them (for example, say a type generic over an Iterator and Index: one
of them can be I and the other one would be…J?)

~~~
nickmqb
Good point, I agree that's a bit of an awkward case. It's something I'm
planning to keep an eye on. If this becomes a problem in practice, we can do
something about it.

------
ahaferburg
Very cool project! Thank you for taking the time to prepare this reveal so
well. Examples and roadmap and all. Good job!

It looks like you started with writing an interpreter in C#, then wrote the
compiler in the language itself. Which is a cool concept, I must say. I can't
fully wrap my head around the practicalities, though. Do you compile the
compiler with an older version of itself during development? Do you plan to
update the interpreter as the language evolves?

 _Builtin serialization of any type_ Couldn't this be implemented with compile
time evaluation in the standard library?

 _Variable redeclarations_ Why?

What about a build tool? Is an interface with the compiler going to be part of
the compile time evaluation?

How long have you been working on this? Is this your first language project?

~~~
nickmqb
Thanks for the kind words!

Correct, the initial compiler is built using the C# interpreter; the compiler
can then be used to compile the next version of itself. Bugs and backwards
incompatible language changes can be a problem though. For that reason, it's a
good idea to keep the C# interpreter updated so there's always a safe path to
bootstrap from.

Variable redeclarations may be useful in certain scenarios, e.g. redeclaring a
variable of type Maybe<T> as T. You're right that serialization may be better
implemented as a library using some kind of reflection capability, this needs
some more thought. I haven't yet thought about the specifics of build tools,
but I very much want to make it easy for people to write tools, and offering
the compiler as a library will be part of that.

I started with the initial idea for Muon late 2017/early 2018. Initially, the
plan was to create a higher level language (more like C#), but the more I
iterated the more I realized that low-level was the way to go. Muon is my
first large language project, though I've always been interested in compilers
and have written some toy languages in the past.

------
travisgriggs
Zig has had a bit of HN traffic in the past year. Any thoughts on how these
two compare with each other?

~~~
nickmqb
There's quite a bit of overlap with Zig (and maybe even more with V, which
recently appeared on HN).

Some differences between Muon and Zig:

\- Syntax/ergonomics: Muon has return type inference, Go-like syntax/no
semicolons, no "pub fn"s anywhere, reference type notation.

\- Memory management philosophy: Muon makes it easy to allocate memory, e.g.
via "new" operator and fully customizable thread-local allocators.

\- Minimal error handling: Muon provides abandonment (i.e. panic), but no
extra error handling features like Zig does.

\- Muon is a more minimal language in general: no meta programming (yet, at
least), no async, no bit fields.

Zig is a lot more mature though, so I'm trying to catch up as fast as I can
;).

~~~
AndyKelley
I'm jealous of that self hosted compiler! For Zig I've mandated that we have
to forever maintain both the stage1 C++ compiler as well as the self-hosted
one (which is not feature complete yet).

Also I have some bad news for you...

> One major caveat: after the above stages have finished, a C compiler still
> needs to run to generate the final binary, which usually takes up the most
> time. The LLVM backend will (hopefully) reduce this.

Nope. LLVM is about 80% of the time that it takes to compile Zig code. Other
language authors have found similar results. See for example
[http://pling.jondgoodwin.com/post/compiler-
performance/](http://pling.jondgoodwin.com/post/compiler-performance/)

Anyway, Godspeed. I'll be watching Muon with interest :-)

Edit: one more question, if I may:

> No undefined behavior. Undefined behavior can lead to various, hard-to-spot,
> bugs. In Muon, all behavior, including platform-specific behavior, is
> defined.

> The compiler currently outputs C code.This means that we inherit C's
> undefined behavior model, which goes against the goals listed above! An LLVM
> backend is in the works which will avoid any undefined behavior.

> High performance. Strive for parity with C.

How are these things to be reconciled? First of all LLVM IR and C have
basically the same undefined behavior model. In both C and LLVM IR it's
possible to output well-defined code. But more confusing to me is how it's
supposed to be fast, if everything is defined?

For example: [https://godbolt.org/z/7d8RLG](https://godbolt.org/z/7d8RLG)

    
    
        export fn add1(a: i32, b: i32) bool {
            return (a - b) > 0;
        }
        export fn add2(a: i32, b: i32) bool {
            return (a -% b) > 0;
        }
    

In this Zig code, we see 2 implementations of a function. `add1` has undefined
behavior if `a - b` overflows. `add2` has defined behavior: wraparound
arithmetic. The assembly code, with optimizations on:

    
    
        add1:
            cmp     edi, esi
            setg    al
            ret
    
        add2:
            sub     edi, esi
            test    edi, edi
            setg    al
            ret
    

You can see that the version with undefined behavior has 1 fewer instruction.
How can you expect to compete with C (or Zig's) performance without undefined
behavior?

~~~
nickmqb
Hey Andrew, I really like your work!

In addition to the Muon compiler I'm still maintaining a C# interpreter which
is used for bootstrapping (just so there's always a safe bootstrapping path),
so there's some extra work there. The interpreter only supports a subset of
the language, which means that I have to selectively avoid a few language
features in the compiler. It's not too bad though.

I've been digging into LLVM over the last week, and I'm slowly coming to that
conclusion. I'm tempted to add an x86_64 backend, but there's already so much
work to do ;). Perhaps WASM would be an interesting target to add. It's new so
it doesn't have too much cruft yet, and it may actually work as an
intermediate representation (haven't looked at this at all though).

Re: edit. Integer overflow will be defined as 2-s complement wraparound. In
your example, Muon would always emit the extra instruction, even in optimized
builds. I must admit that I don't have a good intuition yet for how often
these cases happen. If it means that Muon is leaving significant performance
gains on the table, I'd be willing to consider adding a construct that would
allow undefined behavior for very specific sections of a program to enable
these optimizations. But by default, there would not be any undefined
behavior.

~~~
AndyKelley
Thanks for the compliment. I look forward to watching Muon's progress, and
stealing all your good ideas! (I mean that in a friendly way; both Zig and
Muon are MIT licensed and of course Zig's ideas are all up for grabs too)

~~~
nickmqb
That's entirely fair game, good luck to us both! ;)

------
nymanjon
One thing that is missing from Go is computation expressions (in C# 7.0
async/await was generalized to a computation expression). Computation
expressions are extremely powerful feature in F# which introduced the idea of
async/await. If it is generalized then you can properly handle code failures
better.

In the Go community they have found returning the result/error tuple is
tedious enough that people don't properly handle their errors. I would argue
this is because of the lack of generics and computation expressions.

In F# I can do something like this:

    
    
      let result : Task<Result<MyObject, Error> = asyncResult {  
        let! validatedObject = validateObject object  
        let! dbObject = GetSomethingFromTheDatabase object  
        return dbObject  
      }
      
      doSideAffectLastCodeAndHandleAnyErrors result  
    

This lets you create little DSLs and code that you can handle errors in a
central location and in your main code just get the work done without thinking
about it. Makes for a really great workflow.

See also [https://github.com/louthy/language-
ext](https://github.com/louthy/language-ext)

------
gizmo
This looks promising. We haven't seen a lot of new languages lately that could
turn into viable alternatives to C. Muon binaries don't seem to have a runtime
or other dependencies so it looks like a module written in Muon can be
statically linked into an existing C/C++ project. Pretty cool.

~~~
nickmqb
Yes, one of the goals for Muon is to make it easy to use Muon code from
another language.

There's a recent wave of new languages in the same spirit as Muon, like Zig,
Kit, Jai and V (the last two have not yet been released). But since they all
have no/minimal runtime, it should be relatively easy to reuse libraries
between them.

~~~
vedantroy
I noticed Rust was not your list of languages. Is Rust fundamentally different
then these languages? If so, why?

~~~
nickmqb
Ah, good question. Rust could definitely be in that list as well! It's a more
mature language (so that's why I didn't mention it in the list), but
conceptually it very much fits.

~~~
e12e
Seeing the original list, my guess was that it was no gc, with a small runtime
- and the immediate question then was - how is this different/better than "D
as a better C"?

~~~
nickmqb
The main difference is simplicity. D has a lof of features, like classes,
inheritance, exceptions, properties, etc. Muon is a much simpler language that
specifically avoids all of those things.

------
indentit
I guess a killer feature would be an integrated "package" manager like .NET's
NuGet. Being able to easily "import" C dependencies that way, along with other
projects written in Muon could make life much easier. I'm thinking like Rust's
Cargo, but without the nonsense of having to build all the dependencies
oneself which takes time and storage space - just using a pre-built versioned
release would make a massive difference to workflows and reduce friction. Is
anything like this in the works/on the roadmap? (I didn't see it but maybe I
just missed it, while browsing on mobile ;))

~~~
nickmqb
Short term, I have some tools planned for automatic generation of foreign
function definitions based on .h files (currently you have to do this by hand,
which is tedious and also somewhat error prone).

Long term, I agree that a package/dependency manager would be great! There's
already so much work to do though, so this will unfortunately be out of scope
for the near future.

~~~
indentit
Understood, thanks :) it'd be great to have an issue on GitHub I could
subscribe to for notifying me when your auto FFI generation tool is ready, it
would give me a greater incentive to try Muon ;)

~~~
nickmqb
Done :)

[https://github.com/nickmqb/muon/issues/2](https://github.com/nickmqb/muon/issues/2)

------
voldacar
I haven't looked too deep, but this language seems to have a lot of features
and goals in common with Zig, are the two projects in any way related?

edit: just saw your relevant comment :)

------
indentit
This looks to have been well thought out. I generally shy away from C/C++, I
prefer the GC approach of C#. (I have also coded a little Rust but find it
challenging.) But looking at the docs and examples, I'm very tempted to try
Muon! I like the lack of undefined behavior very much, even if it only
transpiles to C atm; I look forward to the LLVM target.

------
dev_zero
This is a really interesting project and I like where you're taking it. I've
played with Nim for a number of years, and I really like the no-runtime aspect
of Muon and the flexibility and simplicity that you've created with it. Unlike
many others in this thread, I like the whitespace decisions you've made,
though I go in the opposite direction - have you thought about making braces
optional?

Your roadmap looks great as well. Have you considered any of the following?:

\- Lisp-style Macros (e.g. Macros as functions that transform an AST object)
-- this would also answer many of the preprocessor questions you had,
especially if you can specify compile-time behavior (like in zig)

\- Github-based decentralized package management system

\- Owned pointers memory management options

~~~
nickmqb
Thanks! I don't think we should lose the braces. Braces help avoid mistakes
when refactoring/copying/pasting code, and they make the language look more
familiar to people coming from C-like languages, which I think are large
enough advantages to justify having them.

I'm considering adding some meta-programming/reflection capabilities in some
form, if they don't increase the complexity of the language too much. Lisp-
style macros would be a possible approach, compiler plugins would be another.
The latter approach might be easier to implement though.

A package manager will be out of scope for at least the near future, but long
term it would be great to have one!

Re: owned pointers. I'd recommend that people look into more coarse grained
memory management strategies first (e.g. arena allocation), and use one of
those if possible. However, these strategies definitely have their
limitations, which is where alternatives like owned/shared pointers could come
in. However, to implement a "proper" owned pointer, e.g. an alternative to
std::unique_ptr<T>, we would need destructors (which Muon will never
implement) and move semantics (of which there is a chance that Muon _might_
implement it, but probably not anytime soon). With move semantics we can
perhaps create a lighter weight owned pointer that is still useful; I'll have
to experiment a bit to see how that would work out.

------
lostmsu
What about threading and a corresponding memory model?

~~~
nickmqb
Right now, you'd have to use OS-specific threading APIs for that (via foreign
function declarations). I'm planning to add threading support to the standard
library to make it easier to write cross-platform multi-threaded code. Also on
the roadmap are basic concurrency primitives, like channels.

I haven't decided on what I will do in terms of a memory model: likely, I will
add acquire/release constructs to the language, which should be enough to
implement things like lockfree data structures as libraries.

------
baby
I feel like there is a lot of lessons that one MUST take into account if they
want to create a new PL. Not getting inspiration from Rust or F# should be a
mistake.

Sadly Rust also didn't take inspiration in Go's std library.

~~~
apta
> Sadly Rust also didn't take inspiration in Go's std library.

golang's stdlib is nothing special, and even has bad practices, such as weird
casting to arbitrary interfaces and hoping it does the right thing.

~~~
hu3
I think parent refers to Go std library comprehensiveness.

While Go comes with batteries included, Rust leaves up to community libraries
to fill in the blanks for lots of basic functionalities such as regular
expression for example.

~~~
apta
golang is not that special in that regard either way then, see: Java, Python,
C#, etc.

~~~
hu3
I don't think your parent said Go's std lib was special. Just that Rust could
have learned from it.

------
F-0X
I need to get this gripe off my chest. I want types on the left, and not
optional.

I read the main function first. I saw that call to countOccurances, without an
argument. I was confused how it was going to count occurances if it didn't
know what it was looking for. Then I noticed the implementation of this method
above.

Had there have been a type of Map<string, int>, I'd have reasoned what it does
easily.

I hate type inference, it does not help.

~~~
laszlokorte
Here the problem seems to be especially that the _return type_ is infered.
Usually the return type of a function is regarded as belonging to the
functions interface and should be accessible without reading the
implementation.

~~~
nickmqb
Muon always allows you to specify a return type for clarity, though. The key
point is that you have the option not to. Not all code is created equal. For
library functions that are used by many others it is probably a good idea to
add explicit return types, but if you're quickly iterating on a prototype it
can be nice to not have to specify them.

I should also add that a language server will really help here. The tooling
can just tell you what the return type of a given function is. I can also
imagine building some tooling that automatically adds return type annotations
if they are missing (which would be quite easy to build). Such a tool could be
used as a commit hook to enforce a certain coding standard for teams that
would prefer this.

~~~
int_19h
The problem is with contract versioning. If you're writing a library, and your
return types are derived rather than explicit, it's way too easy to break your
clients by changing the type without even noticing. So maybe require it for
public interfaces?

~~~
nickmqb
I'm not yet convinced to make it absolutely required, but some kind of
warning/linter message would definitely be useful for this.

------
kovrik
In the 'Glimpse of Muon' example, how does the Map::getOrDefault() function
work? Shouldn't it accept 2 args: a key and a default value (if the value
associated with the given key is not found)?

~~~
nickmqb
Sibling comment (granzymes) is correct, also see implementation here:
[https://github.com/nickmqb/muon/blob/d557c18d2dcd3fc15a7a7a6...](https://github.com/nickmqb/muon/blob/d557c18d2dcd3fc15a7a7a6ed6dbfa0fc76f400b/lib/containers.mu#L487)

In Muon, you can always add more functions to an existing namespace, so if you
wanted to have a function with that behavior you could add it.

------
iamcreasy
> "Data oriented. Just functions, structs and enums. NO: classes, inheritance,
> properties, etc."

Does it mean C is a data orientated language?

~~~
Crinus
Yes. Many people who go towards data oriented designs (not to be confused with
data driven) implement them in C (or very C-like C++).

------
Crinus
I like it in general, but i have a few comments:

1\. "Data oriented. Just functions, structs and enums. NO: classes,
inheritance, properties, etc."

I think you should allow for struct "inheritance", at least in an Oberon-style
manner. There are many cases where having a common "head" between structs is
useful (e.g. widgets for a GUI toolkit, object types for a scripting language,
etc).

2\. "Memory is initialized to zero. Array bounds are checked (can be turned
off where needed)."

It is not clear from this, but how can both be disabled in a case-by-case
basis? For example in D local variables are initialized to zero by default but
you can do something like (IIRC): Foo foo = ? (or = nil, i do not remember)
which the compiler interprets as "do not zero-initialize this). Similarly can
i do something like array[foo] (bound checked) and array{foo} (non-bounds
checked, imaginary syntax)?

3\. "An LLVM backend is in the works which will avoid any undefined behavior."

LLVM inherits most of C's undefined behavior, so it will not solve your
problem there - you'll need to explicitly generate code that works around
undefined behavior. This is a problem that the Free Pascal developers also
faced (the language has much less undefined behavior than what LLVM assumes).

4\. I am not fan of the language enforcing code style (e.g. "The name of a
type parameter must be a single uppercase character." and significant
whitespace)

5\. Nitpick, but "List<T> struct #RefType { dataPtr pointer count int capacity
int }" is a dynamic array, not a list (also, an example of why significant
whitespace isn't a great idea :-P)

6\. "Type inference for function return values and locals"

I actually prefer to explicitly see the data types passed around, it helps
better understand what exactly the code is doing (not just the algorithms, but
also the data types which are important) without needing to have too much
"implied code" in my mind (i.e. with explicit data types i don't need to
"know" that "foo" returns "bar" when looking at its use in a diff (or any
other non-syntax highlighted, non-code aware code viewing context), i actually
know it because i see it in the code).

(FWIW for this reason i also dislike auto in C++ and similar constructs in
other languages - you save a bit of short term typing now, for a lot of long
term headache later)

7\. "fgets(s pointer #As("char _" ), size int, stream pointer #As("FILE _"))
pointer #Foreign("fgets")"

I think you need to make your type system good enough to natively express C
types, otherwise the string-based pseudotypes will bite you hard later when
you want to move away from using C as a backend.

8\. I do not see any mention of a preprocessor, how is code supposed to IFDEF
stuff (like, e.g. a commercial program that needs to have something like IFDEF
DEMO, or a source file that needs to behave slightly differently if linked as
a static library or dynamic library, or the use of two APIs like e.g. IFDEF
LINUX, IFDEF WIN32, IFDEF OPENGL, IFDEF VULKAN, etc) or work around language
limitations (like the non-availability of inherited structs mentioned above
that C programs often work around by using a macro that defines common
elements for structs)?

9\. "Number literal suffixes"

I'm not fan of the underscore in the suffixes and also i'd avoid the uppercase
L and introduce an i (that is 'nop' but could be useful for automatically
generated code via scripts and/or macros).

There are other minor nitpicks, but i'd need to actually check it in practice
to write more :-P.

~~~
nickmqb
Thanks!

1\. Right now, you can emulate this with the transmute builtin function. Not
yet sure if the additional complexity of this feature would be worth it.

2\. Array bounds checks can be avoided by using the unchecked_index builtin.
Uninitialized locals are not yet possible, but I think it makes sense to
eventually add a construct to allow that, for situations that need every last
bit of performance.

3\. I've only started to look into LLVM recently, thanks for the heads up.

4\. See my comment here:
[https://news.ycombinator.com/item?id=19599908](https://news.ycombinator.com/item?id=19599908)

5\. C# programmers would like to disagree with you ;). Note that the
formatting issue is not due to significant whitespace, as you'd have the same
issue writing up a Golang snippet.

6\. Editor support (specifically, language server) should help a lot here.

7\. The string based types are only used for inserting the right cast to make
the C compiler happy, so they should not be an obstacle.

8\. Compile time evaluation can be used for some cases. Adding a preprocessor
comes with its own set of tradeoffs, haven't yet decided what to do there.

9\. A lowercase l looks a lot like a 1 in many fonts, hence the uppercase L.
Will consider adding an i suffix.

~~~
Crinus
1\. The point of this is to avoid repeating the common headers and have the
type system validate that a pointer/reference of a subtype is compatible with
its parent type (in C you'd need to typecast which can be dangerous).

5\. I also tend to name dynarrays as lists, but in this case you have both
List and Array and the only difference seems to be that one has a capacity
while the other doesn't. Having both can be a bit confusing when you actually
wanted a (linked) list.

6\. It will only help when inside the editor, hence why i explicitly wrote
"when looking at its use in a diff (or any other non-syntax highlighted, non-
code aware code viewing context)".

8\. None of the examples i mentioned can be solved with compile-time
evaluation (except some cases of IFDEF DEMO) since they change what code is
actually compiled (e.g. if you want to use an API that may not even be
available on a target - like e.g. DirectX on Linux) or generate code
themselves.

9\. I do not think it makes much sense to worry about that, there are fonts
that have 0 and O look the same, let the user fix their fonts, do not punish
those who use unambiguous fonts with inconsistency. Trust your users.

~~~
nickmqb
1\. That's true, I didn't mean to imply that transmute is equivalent, there
would definitely be benefits to the feature you're suggesting. But it also
brings additional complexity of course.

6\. Sorry, I totally missed that part of your comment! A language server won't
help in that case. At least wrt to return types though, you're always allowed
to specify them for clarity. I can imagine building some tooling that
automatically infers and adds explicit return type annotations if they are
missing. You could run this as a commit hook if you wanted to enforce that as
a coding standard.

8\. Right, I was referring to the IFDEF DEMO case (assuming it's used to
implement straightforward feature flags). I agree that for the other cases
that you mentioned we'd need something else. For now, for some situations, you
could compile with different files (e.g. gfx_opengl.mu, gfx_directx.mu, etc.),
though it's a pretty crude solution, especially if you want to combine
conditions (e.g. Linux + OpenGL). Definitely thinking about this.

------
cozzyd
The last thing I'd want is a heavier and less stable Electron. Do pointers
decay automatically in Muon after 2.2 us?

On a more serious note, as a particle physicist, I hope this trend of naming
software after fundamental particles ends soon...

~~~
gizmo
Given that particle physicists named their particles "up", "down", and
"strange" I don't think they have the high ground here.

~~~
craftinator
Ouch. I think you'd best not argue this one, cozzyd.

