12 ms·
Show HN: Muon, a low-level programming language inspired by C, C# and Go
- deleted 8y ago[deleted]
- gizmo 8y agoThis 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.
- xixixao 8y agoHave you seen Nim?
- gizmo 8y agoI've used it for some hobby projects a year or two ago. Unfortunately the compiler was very unstable back then and linking Nim into existing projects turned out to be pretty painful, in part because of the gc/threading model. There is a lot to like about Nim, but I think it tries to do too much.
- azhenley 8y agoI second taking a look at Nim: https://nim-lang.org/ https://nim-lang.org/ Some people will argue it isn't a contender for C though since it defaults to using garbage collection.
- lasagnaphil 8y agoBut you can turn off the GC and use it as a ‘better C’ (Nim supports raw pointers by the way.) Although you have to ditch most of Nim’s standard library, you can still import C libraries, including libc (I’ve heard you can also do this for the D language.)
- nimmer 8y agoOnly part of the stdlib depends on GC. Also the GC runs independently in each thread and can work with deadlines, making it harmless in most use-cases.
- nickmqb 8y agoYes, 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 8y agoI noticed Rust was not your list of languages. Is Rust fundamentally different then these languages? If so, why?
- nickmqb 8y agoAh, 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 8y agoSeeing 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 8y agoThe 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.
- IshKebab 8y agoRust aims for (mostly) memory safely which none of the others do. As a result it is way more complicated and rather different from them.
- Gibbon1 8y agoI think that is a very good design choice. Failing at that was one of the big catastrophes with C++.
- cozzyd 8y agoThe 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 8y agoGiven that particle physicists named their particles "up", "down", and "strange" I don't think they have the high ground here.
- craftinator 8y agoOuch. I think you'd best not argue this one, cozzyd.
- Waterluvian 8y agohttps://ablakey.github.io/periodic/index.html https://ablakey.github.io/periodic/index.html On that topic I made this a while ago to make fun of it. (no longer renders nicely on mobile unless you go landscape)
- lostmsu 8y agoWhat about threading and a corresponding memory model?
- nickmqb 8y agoRight 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.
- jph 8y agoI 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 8y agoThat'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 8y agoI 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?
- indentit 8y agoThis 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.
- saagarjha 8y agoMore interesting link, I think: https://github.com/nickmqb/muon/blob/master/docs/muon_by_example.md https://github.com/nickmqb/muon/blob/master/docs/muon_by_exa... 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 8y agoCorrect, 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 8y agoMy 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 8y agoGood 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.
- F-0X 8y agoI 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 8y agoHere 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 8y agoMuon 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.
- ahaferburg 8y agoVery 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?
- semigroupoid 8y agoIt 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? This is called bootstrapping [0] and is common in compiler development. [0] https://en.wikipedia.org/wiki/Bootstrapping_(compilers) https://en.wikipedia.org/wiki/Bootstrapping_(compilers)
- nickmqb 8y agoThanks 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 8y agoZig has had a bit of HN traffic in the past year. Any thoughts on how these two compare with each other?
- nickmqb 8y agoThere'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 8y agoI'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?
- tagrun 8y agoI have a criticism regarding how basic data types are named: https://github.com/nickmqb/muon/blob/master/docs/muon_by_example.md#core-types https://github.com/nickmqb/muon/blob/master/docs/muon_by_exa... 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 8y agoYup, 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.
- voldacar 8y agoI 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 :)
- baby 8y agoI 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 8y ago> 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 8y agoI 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.
- baby 8y agoWat. Do you know a language that offers a better std library? Rust doesn't even have a standard tls or a random crate.
- apta 8y agoJava, C#, Python.
- austincheney 8y agoThis 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.
- jaredklewis 8y ago> * 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. Distributing source code with significant white space seems like a totally solved problem to me. When has this come up for you? > 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. Why would there by more types of line terminators in the future? We already have two ways to do it, which is one too many. Who would add another? And if they did, why would all the text editors and software out there in the world be expected to adopt it? > * 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. Personally, I don't care about people's subjective opinions or what makes them happy. I just want to be able to easily understand what the code does. I find it easier to understand code when it is formatted in a way that I'm familiar with. If each programmer has their own little formatting idiolect, that makes it harder for me to read their code. So, I think it's great when standardized formatting is built into languages, either with formatters (like go and elixir have) or by having a syntax that enforces uniform formatting (as is the case here).
- kovrik 8y agoIn 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)?
- granzymes 8y agoIf the key isn't present in a Map<K, V> it returns the default_value of the type V (which is 0 for integers).
- nickmqb 8y agoSibling comment (granzymes) is correct, also see implementation here: https://github.com/nickmqb/muon/blob/d557c18d2dcd3fc15a7a7a6ed6dbfa0fc76f400b/lib/containers.mu#L487 https://github.com/nickmqb/muon/blob/d557c18d2dcd3fc15a7a7a6... 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 8y ago> "Data oriented. Just functions, structs and enums. NO: classes, inheritance, properties, etc." Does it mean C is a data orientated language?
- Crinus 8y agoYes. 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 8y agoI 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.
- indentit 8y agoI 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 8y agoShort 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 8y agoUnderstood, 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 8y agoDone :) https://github.com/nickmqb/muon/issues/2 https://github.com/nickmqb/muon/issues/2
- dev_zero 8y agoThis 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 8y agoThanks! 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.
- nymanjon 8y agoOne 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