Nature Programming Language
15 points by kana
15 points by kana
I’m not super fond of terms like “natural” for programming style in a language because what is “natural” is highly subjective.
That aside I am disappointed the the programming model isn’t some how centered around manipulating trees :)
Honest to god, I will never ever touch a language with try catch semantics ever again. Sum or nothing.
try catch semantics Sum or nothing
Why not both try-catch and sums?
Honestly interesting, how is that implemented? In general im thinking rust error handling vs java error handling, since i am currently maintaining both of these at $WORK
So the thing to realize here is that try-catch and sums are two orthogonal things. You can have both, just one, or neither.
In Fir we have both. In Java we have just try-catch. In Rust we have sums but not try-catch. (though note that variants in Fir are more flexible than Rust enums)
try-catch is a way to transfer control from one code (the throw use site) to another (the catch block).
Then the question is what kind/type of values to transfer from throw to catch. That's a separate concern.
In Fir, you don't have to throw a variant, you can throw anything. If you look at the type of throw in the blog post it doesn't take a variant argument. But if you throw a variant you get to do all the nice things shown in the blog post.
The try-catch implementation is the same one in any other language: you unwind the stack, find a catch block. (Edit: we don't have try-catch blocks, try is a function that returns a Result, but of the try function is basically the same as try-catch blocks in other langs)
Edit: forgot to mention the typing part of it. That's also not novel but not used by any of the mainstream languages.
my main issue with exceptions is readability and control flow, id much rather use values returned from functions to encode errorstates, than use implicit flow and invariants to archive the same. of course there are different ways to go about this, but i consider go being almost at the peak of error handling and rust being on top of the hill.
I like ocaml's approach better than rust's - the language provides both exceptions and sum types and tries to make them both ergonomic so you can pick the one that suits any given situation best.
Java does have sum types nowadays with sealed types. (They are not tied to the exception system.)
I’m fine with exceptions as long as the ergonomics are good. It makes it much more clear when failure is expected vs unexpected which can help guide you in how to write the code - e.g do you want to handle the failure case or consider the failure to be an “It’s reasonable to just terminate”.
It’s also helpful when interacting with other languages as you otherwise have to catch the throw on the interface between the languages, propagate it up, and each level needs to know whether a given error should be turned back into an exception later on.
I think the swift syntax makes the common cases easy with try! and try?
That out of the way I think many languages with exceptions grossly over use them. I remember using antlr many many years ago, and on .net at least it used exceptions for control flow, and holy heck .net did not like that if you were debugging (runtime went from seconds or less to 10s of minutes)
very much a nitpick but I really don't get why languages often choose this inconsistency where you have the types be defined before or after a identifier name in different parts of the language or you have different aspects of the language do statements for your constructs but others not.
why do
fn main(int x): int {}
instead of
fn main(x int) int {}
or why have functions be their own standalone statements but have interfaces not be so. It just feels weird to me. In my opinion, if you're gonna do something, go all the way. I'd appreciate it if people could come and try to "convince" me of why languages are doing this at all
It seems like nearly every modern language uses “name: type” declarations (with Go omitting the colon just because it has to do everything differently.) The “type name” style used by C/C++/Java creates a bunch of syntactic problems.
yes I'm aware, that wasn't the question tho, I'm asking why instead of just sticking to either style, they're choosing to mix and match both inconsistently.
Look at these valid constructs from the language:
fn add(int a, int b): int {}
int a = 5;
type Adder = interface {
fn add(int a, int b): int
}
type Number: Adder = struct {}
why fn is it's own statement but the others are not? why are the types before the identifier name but after in functions? why is the same exact syntax used to indicate "this implements this interface" but it looks exactly like a type declaration? it's just not very consistent and I can't think of any reason to do it this way. It just seems like the author wanted to have types before identifiers but didn't want to go through the trouble of writing a parser that's able to disambiguate so they chose some murky middle ground
the inconsistent : is likely because of parsing ambiguities that type name causes for function types specifically, because the identifier after fn() is ambiguous. fn() ident could mean ident: fn() -> () or fn() -> ident. Other types have known length, so they don't need a terminator/continuation char.
As for why not some more consistent solution everywhere instead — they seem to be fond of the C syntax. It looks like instead of inventing an internally-consistent approach, they took C and fixed only a few things (type and fn prefix and this return type hack). Their type = looks like a cleanup C typedef, not an idea of global names being assigned items.
Interesting.. If I'm reading their "generics" example correctly, you'll get C++ template experience rather than Rust generics when dealing with their generics: generics in this language are not checked before instantiation. The relevant part from the example:
type box<T, U> = struct {
T width
U length
}
fn box<T, U>.perimeter():T {
return (self.width + self.length as T) * 2
}
Nothing here says length and width can be added together with + and the result can be multiplied by 2. So they can't check this function at the declaration, they need to check each application of it.
Not sure this is a good idea in a new language tbh.
seems like it's very golang-inspired, so a comparison page would be interesting.
I also wondered why I would want this given go is mature and exists.
At the first glance:
no if err != nil noise, although it's merely a try/catch instead. At least functions must declare if they throw or not.
slightly better nullability story than Go, but I don't think they can losslessly handle Option<Option<int>>.
match statement, although they don't have arbitrary sum types, only type-based tagged unions.
.await() suggests there are explicit promises, so maybe it will eventually have more structure than everything being DIY'd from channels.
but I don't see any killer feature. It's still a GC language, with no word on data race safety, and it lacks deterministic destruction. Their mutex doesn't protect its content from being accessed without locking, and you need to unlock it entirely manually.
This looks really cool, expecialy as i really like both rust and golang. The style of exception like try-catch with result-like semantics is really interesting and I was waiting for more languages to try it out.
Does anybody know some background/context on why this language was created?