Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Show HN: Inko – A safe and concurrent object-oriented programming language (inko-lang.org)
95 points by YorickPeterse on Aug 6, 2018 | hide | past | favorite | 45 comments


Author here. Both the language and website are still very much a work in progress, so I'm happy to answer any questions here.


Looks pretty good. I see someone has been reading "The Error Model" by Joe Duffy, eh?

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?


For those who haven't heard of Joe Duffy's error model post (like me):

http://joeduffyblog.com/2016/02/07/the-error-model/


    > 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.


Just of curiosity, how does the error model of Midori relate an advanced exception system, such as that found in Common Lisp?


I haven't used Common Lisp, so sadly I'm not sure if the two are related (or how much).


> data race conditions impossible.

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.

http://zproc.readthedocs.io/en/latest/user/atomicity.html


    > 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.


Happens with me too. I sometimes don't realize how my words come out.

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.


That sounds great :)

Have you ever looked into Ponylang? Would you be able to do a similar comparison?


I have only briefly looked at it to see how their capabilities system works. I'll see if I can add it to the comparison page (https://inko-lang.org/manual/inko-compared-to-other-language...).


Thanks for the detailed explaination.


zproc looks like am interesting approach to concurrency in python. For anyone else curious about it, here is the github project:

https://github.com/pycampers/zproc


Honestly though, it Just a really fancy wrapper over zmq.

I just gave everything I learned from the zmq guide a pythonic interface. :)


Couple of questions:

- 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...


When I saw `try`, the first thing that came in my mind was Zig language; that's where I first saw this:

https://ziglang.org/documentation/master/#try


> What is your plan on package management?

I wonder how avoid to build one, and just reuse a good one. How feasible is it?


As far as I know it technically is an option, just not a very nice one. Most package managers tend to be _very_ specific in how they do things, making it hard to extend them beyond that. Arch Linux' package manager might be an exception, but I'm not sure how reusable it is.


At a quick glance it seems like you're adopting the Active Objects model, is that correct? What are your thoughts on protocol safety? Methods being static mean that we can't guarantee protocol adherence, but perhaps that's overkill?

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).


Not exactly. Inko doesn't have an RPC mechanism built in, at least not like languages such as Pony. When you send a message to a process you just send it some arbitrary object (a string, array of integers, etc), and it's up to the receiving process to act upon it in some way.

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.


I don't quite get how messages and method calls interact. Could the process intercept the method call by explicitly receiving or somehow influence the process module to do something else?

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.


Thanks for the details! The question about channels stems from the fact that in pi-calculus it's possible to have different processes read from the same channel - making it unsuitable for distribution. Your implementation of channels seems to be extremely similar to what I did! Keep calling it channels ;)

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.


Mailbox types for unordered interactions require manual management of mailboxes (new/free) but the type system guarantees that you can't free a mailbox with messages still in it. Garbage collection is probably possible but it seems difficult.


Why did you decide to license under MPL? Just curious as I haven't seen many projects licensed this way. Most projects go MIT or Apache2.


Details on this can be found at https://inko-lang.org/faq/#header-why-does-inko-use-the-mpl-....

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.


Well it looks like somone finally got all the pieces right for a true OOP as originally envisioned. Look forward to seeing it mature.

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.


What's the intended niche for this language? What types of programs do you expect to be written in it?


I see it as a competitor to languages such as Python, Ruby, and Node.js, specifically for writing web services. Eventually it would be a competitor to Erlang/Elixir, but there is a lot of work (and especially testing and tuning) to do before we get to that point, as BEAM/OTP is a pretty solid platform.


I think you should really be more focus because ruby and Erlang have very little in common. Go is more for low level programming, Erlang not really, Ruby, is more about expression. NodeJS can t do multithreading... Do you have a problem that your laguage can solve really better than others. Or are you still figuring out?


Ruby and Erlang/Elixir have more in common than one might think, especially with frameworks such as Phoenix gaining more popularity.

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.


Great work! I've started building a YARV compatible VM in Rust so it's always great to see more examples of VMs and GC in Rust.

JRuby actually already has concurrent GC and no GIL.


I was thinking that it could be used to target the ethereum virtual machine... ie: compete with solidity.


I'm not particularly interested in Ether and the altcoin world, and I'm not sure if the way the language works makes it really suitable for that. Having said that, if somebody wants to write their own implementation on the EVM they are of course free to do so.


Congrats Yorick! Super impressive! The site looks great! I'm trying it out and so far its fantastic!


Am I the only person who thinks divbyzero shouldn't take down a whole process?

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?)


A panic will take down the entire system, and print a stacktrace for the process that triggered the panic. I think this is a fundamental requirement, as it dramatically cuts back the amount of exceptions you have to handle.

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.


So even if a panic happens in a subprocess the parent is killed? Why not allow the parent to handle restarting of the child? Maybe I misunderstand.


Panics are not meant to be retried, as they are used exclusively for handling bugs. Examples include: division by zero, sending a message to an object that doesn't support it (if you manage to bypass the compiler), etc. Whenever these occur, your program is broken. Restarting a process in that case won't change anything, as it will run into the same problem again and again.

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.


So these are errors that should have been caught by the compiler but were not?

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).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: