The Claro Programming Language
docs.clarolang.com
docs.clarolang.com
I'll do my best to finally respond to as many comments here that I can get to.
Please, for modern software UTF-8 everywhere is the way to go!
[0] https://docs.clarolang.com/common_programming_concepts/varia...
[0] https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-st...
I think the real answer is to have a String interface and then allow for different implementations. There are times when it should be ASCII 8-bit. Or UCS 4. UCS 2 is reasonable. It's about trade-offs and how much constant width is worth. Just like you can use across different list and set implementations for the algorithmic profiles, you should be able to chose string types. Something UTF8#"Hello World" or EBDIC#"IBM".
Inserting, appending etc, a character that cannot fit into a given string should throw an exception.
> Claro’s declarative, DAG-based structured concurrency model provides an easy-to-use abstraction to statically guarantee that programs are:
Non-blocking
Data-race free
Deadlock free
Optimally scheduled
Scalable by default
Any language that can simplify the static guarantees of all those is worth a look.To eliminate deadlocking, Claro tracks any explicit "blocking" on the completion of a future<T> and requires the containing procedure (and any dependents) to be explicitly annotated as "blocking", and then these blocking procedures aren't allowed to be called within a Graph procedure OR be scheduled on the Executor directly in the form of a lambda for example.
Data races are prevented by simply making it impossible for two threads to share a reference to mutable data. The most obvious consequence here is that mutable data can't be passed into a Graph Procedure, and none of its internal nodes can evaluate to a mutable data type. Less obvious is that lambdas cannot "capture" mutable data. If they were allowed to capture mutable data, then they would effectively become stateful "objects" themselves in a way that the type system doesn't track (a `function<int -> int>` has the same signature regardless of what it captures) so this would effectively be a backdoor to allowing mutable data to leak between threads.
More details here: https://docs.clarolang.com/fearless_concurrency/fearless_con...
Though I guess you can try restarting it with Erlang.
At the moment, with specific regard to Claro's concurrency story, I'd say it's unlikely that there will be any escape hatches for writing obviously thread-unsafe code whatsoever explicitly available to user code.
It's worth taking a look at this section of the docs to see Claro's current stance on the StdLib itself being given the unique right to "bless" certain mutable data structures to be shared between threads after they've been explicitly verified to be thread-safe. https://docs.clarolang.com/guaranteed_data_race_free/guarant...
It also has an upshot that you can execute your quieries on GPU [1].
> guaranteed... that programs are:
Non-blocking
Data-race free
Deadlock free
Optimally scheduled
Scalable by default
And the call that "any language that does this worth a look."SQL (transaction processing) does all this and more for quite some time [1] now.
[1] https://cs.yale.edu/homes/thomson/publications/calvin-sigmod...
I paged through about 20 of the pages. Have we reached a point where “new computer language” means variation on a theme, same pigs, different lipstick? Nothing negative meant. It all looks decently thought out and pretty otherwise conventional. Kind of like yet-another-marvel-movie. It’s different, but it isn’t really.
I miss the days where “new language” meant different ideas and models. Like when it was Scheme and Forth and C++ and Pascal and Ada and ML and Fortran and Smalltalk and Lisp and Eiffel and Beta and Prolog and Self. Those were much more varied days.
Big motions in language theory should probably be expected to diminish in frequency as the field matures. Not every new language needs to do that. Just about every language developer has their own pet language, often more than one. It's a good thing. It's how the field advances.
I would say, generally speaking, that most new languages these days do not solve the big problems of writing software. For the most part, language level ideas that change the big picture of how one interacts with the system are few and far between, and these ideas have mostly already been discovered in older languages. New languages usually even ignore these. For example, Rust's lack of a REPL from the start. And languages like Rust are spending a lot of time refiguring out the wheel and stalling midway through when it comes to library development.
A programming language is defined as much by what it chooses not to include as by what it chooses to include. Functional languages choose to not include mutation and statements, but we don't complain about them "ignoring" those features, it's a very intentional decision to drop them. Golang looked at exceptions and decided to not include them. Not every language should have every feature, both from a practical perspective (how would you maintain such a language?) and an ergonomic one (how would you work in such a language?).
Rust's lack of a REPL wasn't just them ignoring an important innovation, it was them choosing to not implement a feature that they know their primary audience won't use and that doesn't do much to help in the compiler-centric development flow that they're working on supporting. Rust doesn't have a REPL because REPL-oriented development doesn't really suit Rust, not because they forgot that REPLs are a thing.
Rust moving away from its more ML-leaning beginnings is a downside, to me at least.
Rust managed to normalize higher order functions in the systems space, provide a compelling answer for high performance memory safety, and mount the first successful assault on the C/C++ duopoly since C++ was conceived. That's a pretty impressive track record for disruption, and discounting it because it doesn't have enormous groundbreaking ideas and it doesn't have a REPL is pretty short-sighted.
Rust succeeded in pushing back the Overton window of programming languages because it didn't try to innovate in places that didn't matter. A more radical language might be more aesthetically appealing to someone like you but won't change the experiences of any significant number of developers in their day to day work the way Rust has.
FWIW, even in your list you see lots of variations on themes rather that unique ideas/models. Scheme and Lisp are closely linked. Pascal and Ada and Fortran are all conceptually similar, and there's a good amount of cross-pollination between them and Beta and Eiffel and C++. Self is an evolution of Smalltalk. And so on.
Claro seems to focus its efforts elsewhere - the main innovation seems to be its graph-based multithreaded work scheduling which is a novel concept, and has major implications for program organization, just not syntax.
> We don’t support exceptions, all errors must be declared and manually propagated
> however, `panic`
Like, just admit you support a poor man’s version of exceptions.
Similar to how Go took a decade to admit that (1) generics are useful, and (2) generics are already supported, by builtin types
The worst thing is, I've still not even 100% decided myself. I think that does say something about the design of exceptions.
let x = if foo then bar else panic("unhandled case !foo") end
Panic allows you to enumerate cases without implementing them, which can be very helpful in development. As I build out functionality, my code crashes exactly where I need to implement the new case, but I can implement the initial happy path all the way to completion.try/catch on the other hand, I'm not a fan.
Your threads will panic. They will throw exceptions. You will end up dividing by zero at 3 AM on a Christmas.
If your own answer to exceptions is "yeah, the program just crashes and burns", you're doing everything wrong. It took Go and Rust over a decade to realise that and add poor man's excuses for handling panic. But exceptions are inevitable, and your language must provide good ways of dealing with them. See Erlang's approach for how to do it right.
I raised my eyebrows at this part though where the type constraints part was being explained:
> Coming from an Object-Oriented background, you may be tempted to compare Contracts to "Interfaces", but you'll find that while they may be used to a similar effect, they are not the same thing. The intention of an "Interface" is to encode subtyping relationships between types, whereas Claro has absolutely no notion of subtyping. [1]
IME with e.g. C#, interfaces are exactly for this kind of thing? Abstract classes and classes are more used for polymorphism?
But I admit it's been a while since I worked with C# and I may be remembering this stuff wrong.
[1] https://docs.clarolang.com/generics/contracts/contracts.gene...
Arguably, today the line is somewhat blurred because interfaces can have default member implementations (which are used to extend interfaces without making it a breaking change) but generally you are correct. An abstract class and subclasses that inherit from it signifies what the type is while the interfaces a class or struct implements signify what the type does.
[1] https://docs.clarolang.com/generics/contracts/dynamic_dispat...
Source: https://twitter.com/Emma_Hollen/status/1340402040070156299
Perhaps the intent here is for it to mean "offer tool", though.
In the movie this exact translation turns out to be wrong.
I don't like the separation of of interface and implementation at the file level. I feel like having to type the same thing twice header-file-style really slows down development iteration speed.
I can understand where you're coming from with the split between API and impl. It's certainly something I'm keeping on my radar. Personally I care more about readability than writability so it's not a big issue for me, but I can understand that others would land differently on this point.
One reason that I'm holding out on this (for now) is that I'm actually excited about how this one decision makes it quite easy to express some quite complex patterns using "Build Time Metaprogramming": https://docs.clarolang.com/metaprogramming/code_reuse/reusin....
The whole section is really more of an exploration than it is any sort of strong statement that this is what all Claro code should look like. But it's all fundamentally enabled by this "physical" separation between API and impl.
For now, Java code can call into Claro code in a fairly straightforward way. The main hurdles are:
- the namespacing/naming of the Claro code you're calling into is funky because Claro doesn't use Java's "package" namespacing system
- manually constructing non-primitive data to pass to Claro procedures is currently very annoying and, more importantly, unsafe (you could break Claro's type system rules)
In the other direction, Claro's only (current) mechanism for calling into Java directly is restricted to the stdlib's implementation. For example the deque Module[1] exports an `opaque newtype mut Deque<E>` that is actually just a `java.util.ArrayDeque<E>` underneath[2]. The reason this isn't exposed to Claro programs outside the stdlib (yet) is because:
- the type systems are very dissimilar and would need mapping
- Claro doesn't use Exceptions, so you'd have to ensure that any calls into Java code that can throw manually catches and models an error return value[3]
All this said, it's very possible that in the future these limitations can be addressed!
[1] https://docs.clarolang.com/stdlib/deque_module.generated_doc...
[2] https://github.com/JasonSteving99/claro-lang/blob/b5e33ae4ef...
[3] https://github.com/JasonSteving99/claro-lang/blob/b5e33ae4ef...
Our goal is NOT a general-purpose turing-complete language like this one is, but we do some amazing lock-free, DAG concurrency things to achieve the processing wins.
I'm all for a hobby projects, this looks good. No disparagement on the effort.
But I think the community would be better served if all this brain power could get behind a smaller set of languages. We have hundreds of languages now, none of them are going to get enough market share to ever be adopted.
on the second point yes, one the one hand too many languages and too few will see large adoption
one the other hand, competition and activity will drive the popular language to continue innovation and not stagnate, look at C# continuously adding functional features, java also to a lower degree
while many of those new small languages will never see wide use, their existing is pushing the tides
MacOS only.
Also, there are some passionate statements about other languages:
> As it's currently defined, there's nothing requiring the two arguments to actually have the same type. In this trivial example, that may be fine, but if I were to actually want to ensure that two arguments both implement an interface and they both actually have the same type, then I'm out of luck - there's no way to statically encode this constraint in Java!
This is simply not true:
static <T extends Stringify> void prettyPrintPair(T x, T y)
I personally really dislike when people talk about any kind of language feature with strong negativity, while providing their solution as the superior one. There is a high chance that they are wrong, since different features in the same area have different benefits, and usually no one thing is “better” than another, whatever this means.what does that legally mean, if a project on github share the source publicly, but doesnt state the license, is there like a defacto or default license that applies then
You may look at it, but you may not reproduce it elsewhere or create derivative works (except where covered by exceptions).