Show HN: Inko – A safe and concurrent object-oriented programming language
inko-lang.org
inko-lang.org
The example I would like to see is the one that shows the language's object-orientedness and message-passing. Something like "public_send" or "define_method". And maybe show runtime reflection capabilities, if Inko has them.
Also, some questions you might want to answer on the main page/in examples:
* What platforms are supported?
* How big is the standard library?
* How do I encode/decode JSON/XML?
* What's the C FFI story?
* Are there benchmarks?
> Looks pretty good. I see someone has been reading "The Error Model" by Joe
> Duffy, eh?
Yes! It was a _huge_ inspiration. In fact, I wasn't sure what error handling
mechanism to use until I read the article. > Something like "public_send" or "define_method". And maybe show runtime
> reflection capabilities, if Inko has them.
Good call, I'll note this down. Technically this is possible at the moment, but
it's not exactly user friendly.Regarding your list of suggestions, I agree that having those would be nice, and I'll see how I can best add those.
The state of the standard library can best be described as "very young". For example, only recently did I manage to add enough building blocks to build a testing standard library, and one of the next goals is to make sure everything is tested before adding more.
Benchmarks aren't available yet because I think it's too early. FFI is planned, but not yet supported.
- What is your plan on package management? - With concurrent languages I always see a great potential of building something that is better than Go lang and not backed by Google. For that to happen you need native Async IO support. Do you plan to make it part of core? - Do you plan to support something like green-threads, coroutines, or go routines?
Take my opinion as a biased web developer who is looking for a better performing compiled language than Node.js or Go lang.
> What is your plan on package management?
There is no fixed plan yet. My idea is to take an approach similar to CPAN: the registry (where you manage your account, packages, etc) and mirror (where you download the packages from) are two separate systems. This makes it easier to set up your own mirror. The mirror would just be a static file server of some kind (e.g. an S3 bucket). Security wise I was thinking of using TUF (https://theupdateframework.github.io/), though I have yet to actually sit down and read through it. > For that to happen you need native Async IO support. Do you plan to make it part of core?
In Inko, similar to Erlang and Elixir, you don't really need async IO. This is because IO operations are offloaded to a separate thread pool. Inko may one day use async IO operations for this under the hood, though it certainly wouldn't be exposed directly to the language. In practise this means that instead of this: file.open('foo/bar.txt', function(handle) {
handle.read(function(data) {
...
}
}
You just do this: import std::fs::file
let handle = try! file.read_only('foo/bar.txt')
let data = try! handle.read
"try!" here just means "try this operation, panic (= terminate) if it fails". In the above example, `read_only` opens a file in read-only mode, and `read` reads from it. Both are blocking operations, but both run on a separate thread pool. > Do you plan to support something like green-threads, coroutines, or go routines?
This is built-in using the `std::process` module, one of the core foundations of Inko. If you're interested, you can read more about this here: https://inko-lang.org/manual/getting-started/concurrent-prog...I wonder how avoid to build one, and just reuse a good one. How feasible is it?
There's a lot of interesting work going on in this field so to save you some research I suggest looking through at least the related works section of my thesis (see profile).
When you see code such as `process.send`, that means we're sending the "send" message to the "process" module, which ends up calling a corresponding method. This method then sends the actual message to a process.
Channels are a great idea! What capabilities (send on channel/receive on channel) can you forward to other processes? As Cloud Haskell explain, in distributed systems we must not allow send on channel to be forwarded.
Regarding channels, both linear channels and mailbox types for unordered interactions are interesting ideas worth looking into.
Great project!
> I don't quite get how messages and method calls interact.
The term "message passing" is a bit confusing in this case, because it applies
to both objects and actors, but both use a different approach.When you send a message to a regular object, usually a method with the same name is called. For example, when using `process.send` the "send" method in the "process" module is called. This however can change, as objects can implement the method "unknown_message" to handle messages for which no method is explicitly defined.
In case of processes, sending a message is just that: you send a message, this gets put into a special queue, and then it's up to the receiving process to do something with it (whatever that might be). If you want to implement some kind of RPC system you'll need to see what kind of message you have, then dispatch accordingly. Inko's test suite does this using polymorphing: every message the test runner receives implements the "Command" trait, which defines a "run" method. The test runner then simply sends "run" to these command objects, letting them figure things out from there.
> Channels are a great idea! What capabilities (send on channel/receive on
> channel) can you forward to other processes? As Cloud Haskell explain, in
> distributed systems we must not allow send on channel to be forwarded.
I'm not sure if I fully understand this question. A channel in Inko consists out
of two objects: a Sender and a Receiver. The Sender just wraps the PID of the
receiving process, the Receiver is just used for storing the type of the message
at compile time, and providing some methods.As to what you can send: everything that is compatible with the type of the channel. For example:
import std::process
let sender = process.channel!(Integer) lambda (receiver) {
receiver.receive
}
sender.send('foo')
This will fail, because our channel only allows `Integer` values, whereas
"foo" is of type `String`. > Regarding channels, both linear channels and mailbox types for unordered
> interactions are interesting ideas worth looking into.
It is worth mentioning that despite the name "channel" being used for the
method, it's not an actual channel data structure (e.g. like in Go). The
messages are still stored in the mailbox of the receiver, just like when using
`process.send`. Instead, the Sender/Receiver API is just a simple trick that
allows the compiler to know what types of messages are being sent and received.
I called this "channel", because after several hours of thinking I still
couldn't come up with a better name.One thing I didn't like with the approach of storing channel messages in the mailbox is that if you use the regular receive statement you might receive a message ment for the channel. In my work I had to be very careful and use selective receive all the time. My preferred solution is to give each actor multiple inboxes so that unrelated messages don't get conflated. I haven't had time to explore this yet but if you're interested in implementing it in Inko I'll be glad to help.
> My preferred solution is to give each actor multiple inboxes so that unrelated messages don't get conflated.
This is definitely an option, and I wouldn't be surprised if we eventually end up with something like this, though I'm not sure yet how the garbage collector would handle this.Are you sure? Using message passing doesn't magically make stuff immune to races. You Still have to think about atomicity.
Here is an excerpt from the documentation on my multiprocessing library, zproc.
I just gave everything I learned from the zmq guide a pythonic interface. :)
> Are you sure? Using message passing doesn't magically make stuff immune to races.
> You Still have to think about atomicity
Sure, _just_ using message passing doesn't do much. But Inko doesn't have shared memory (well apart from a permanent heap, but that's a different story and it has a very specific purpose). Perhaps I phrased things the wrong way, but the idea is that because there's no shared memory, you can't have data races due to concurrent modifications.Anyway, is there any reason as to why one would not use more mature languages like erlang and recently, dart? They also have the concept of isolates, that can't share memory.
Apart from the written in rust part which is really cool.
> Anyway, is there any reason as to why one would not use more mature
> languages like erlang and recently, dart?
Considering Inko is still very new, right now there isn't really a reason that
justifies using it in production. Of course in a year or two (depending on how
fast things go) that might completely changes.Ignoring the maturity aspect, Dart and Inko are pretty different. Dart focuses more on browsers and mobile (with Flutter), whereas Inko focuses more on backend services (a web app, a key/value store, that kind of thing). In case of Dart the focus directly affects the language, as for example symbol mangling (to compress the source code) means reflection is harder (IIRC Dart's mirror implementation is in a state of limbo because of this).
Erlang is much closer to Inko conceptually, though the two languages are quite different, with Erlang being functional and Inko being object-oriented. Competing with Erlang will be very hard: it's well established, mature, has a healthy ecosystem, etc. Apart from the paradigm, I hope that Inko will eventually have three big benefits over Erlang:
1. A better garbage collector, which translates to shorter pause timings, better use of memory, etc.
2. A JIT. Inko doesn't have one at the moment, but it will have one eventually. How long this takes I don't know. I'm not comfortable using LLVM as its GC support is spotty (at least last I checked it was pretty much still a nightmare), it breaks APIs every release, and every Linux distribution ships a different (incompatible) version.
I'm hoping somebody writes a somewhat decent JIT library in Rust that I can use, but so far there have only been basic building blocks made such as https://crates.io/crates/dynasm.
For Erlang there have been various attempts at writing a JIT, but I believe none of those ever made it to a production state.
3. Inko will be simpler. It's error handling model means you don't have to monitor processes, as they can't crash unexpectedly (unless there is a bug, which you shouldn't handle at runtime). Combined with the gradual typing aspect I can see Inko being much less frustrating to use, be it for a small prototype or a large application.
Have you ever looked into Ponylang? Would you be able to do a similar comparison?
The summary is: it protects the project better than the MIT license does. As far as I know the only difference with Apache 2 is that MPL 2.0 requires that modifications are licensed under the same license.
Note I could not find any docs.
> Note I could not find any docs.
Maybe I should rephrase the Documentation page (https://inko-lang.org/documentation/) a bit to make the link to the manual stand out more. The manual can be found here: https://inko-lang.org/manual/, all the chapters can be found in the sidebar.Note that if you are on a mobile device, you need to tap the "Toggle sidebar" link at the top to display it.
I don't really see Inko as a direct competitor to Go, as Go's compiled nature makes it quite different in use cases. Maybe when we have a JIT and an easy way of distributing the VM + code, Inko could be a competitor to Go.
> Do you have a problem that your laguage can solve really better than others.
> Or are you still figuring out?
Ignoring that Inko is still a very new language, there are already a couple of benefits compared to Ruby such as: no need to pause everything during garbage collection, easier to write concurrent programs, preemptive multitasking (using more than one OS thread under the hoods, unlike both Ruby and Node), more straightforward syntax (at least I think so), etc.JRuby actually already has concurrent GC and no GIL.
And along those lines, how would you envision handling processes that panic, I presume they don't send out Hail Mary messages to sibling processes? Does one process take down them all with the intent of restarting the lot (with an external watchdog?)
Runtime exceptions can't bubble up implicitly, and they can't be thrown at the top-level. This prevents an exception 15 call stacks deep taking down the entire system unexpectedly.
I do agree that zero division errors are a bit of an odd case to deal with, as it's not unlikely for them to happen during runtime. Having said that, in the past 10 years of coding I have never seen code that handled zero division exceptions and didn't just straight up ignore them (something I don't want to promote).
I believe Pony returns 0 for zero division errors. As crazy as this may be, I do somewhat understand their choice. I think integer divisions would be easier if there was something like NaN but for integers.
> And along those lines, how would you envision handling processes that panic
Several years into the future, I envision there is a way to somehow act upon them (e.g. logging them in Sentry or a similar service), but the system would still shut down after this has been completed. I however will not add a mechanism that allows you to just swallow a panic.Expected errors such as network timeouts will use exceptions, and these have to be explicitly handled. For example, if you try to open a file that you don't have access to, an exception is raised.
I'm just thinking generally for production code you have a process supervisor daemon automatically restart when there are fatal errors (out-of memory errors for example). But in languages like erlang that support processes, one of the advantages is that you can support this mechanism through the process model. If I hit a divide by zero error in an edge-case branch of code, I will still want my process restarted no?
> So these are errors that should have been caught by the compiler but were not?
Some of them yes, others no. For example, the compiler can't statically guarantee that there are no divisions by zero. > If I hit a divide by zero error in an edge-case branch of code, I will still want my process restarted no?
Sure, and so Inko may at some point have a system for running "clean up" code of sorts, to report the error, etc. Actually restarting of things is something I prefer to have done by a system daemon, as there is no guarantee a program can continue after a panic (e.g. memory might be corrupted based on the nature of the panic).