8 ms·
The Inko Programming Language
- thatxliner 3y ago> Inko allows...moving of the borrowed values while borrows exist Yet > With Inko you never again have to worry about NULL pointers, use-after-free errors, unexpected runtime errors, data races, and other types of errors commonly found in other languages I’m wondering how that can possibly work? Surely the memory safety guarantees are not as strong as Rust’s guarantees.
- skavi 3y agoI assumed reference counting and locking would be employed transparently as needed.
- ObscureScience 3y agoIf the moved value still is not allowed to be read or mutated while borrowed elsewere I don't trivially see the issue.
- thatxliner 3y agoBut how could you statically check that?
- ObscureScience 3y agoI don't see how it would be different if the owner change, as long as the borrow takes precedence.
- bradrn 3y agoIt looks like there’s a little information here: https://docs.inko-lang.org/manual/latest/internals/compiler/#ownership https://docs.inko-lang.org/manual/latest/internals/compiler/...
- YorickPeterse 3y agoInko defaults to heap allocating objects (except for Int, Float, Nil, and Bool), meaning the data pointers point to is in a stable place. Thus, moving them around is just moving a pointer around, not a memcpy of the underlying data. This in turn means it's fine to keep references around. To prevent you from _dropping_ a value while references still exist, Inko uses runtime reference counting. Owned values being moved around incurs no reference counting cost, but creating and dropping references does (just a regular increment for most objects, so pretty cheap). When a value is dropped, the reference count is checked, and if it's _not_ zero a runtime panic is produced, terminating the program. Refer to https://docs.inko-lang.org/manual/latest/getting-started/memory-management/#references-and-borrowing https://docs.inko-lang.org/manual/latest/getting-started/mem... for some additional details. This setup is perfectly memory safe and sound, though in its current form the debugging experience of such errors (although surprisingly rare, but that might just be me) is a bit painful; something I want to improve over time. My long-term vision is to start adding more compile-time checks, such that maybe 80% of the cases where a reference outlives its owned value is detected at compile-time. For the remaining 20% or so, the runtime fallback would be used. In theory this should provide a good balance and only require a fraction of the mental cost associated with Rust. Whether that will work out remains to be seen :)
- tomp 3y agoThe first bold claim I see on the page is: Deterministic automatic memory management but actually looks like it's neither deterministic (refcounts!) nor actually memory management (deallocating memory can randomly crash the program?! this is even worse than C, where e.g. use after free can crash a program but then you're doing the wrong thing!)
- YorickPeterse 3y agoThe reference counts don't dictate when memory is released, that happens when an owned value goes out of scope, just as is the case for Rust. The reference counts are merely used as a form of correctness checking. The result is that allocations, destructors, and deallocations are perfectly deterministic. Deallocating memory itself doesn't crash the program either, rather it's a check performed _before_ doing so (though that's mostly a case of pedantics). This strictly _is_ better than C, because if the program kept running then you'd trigger undefined behaviour and all sorts of nasty things can happen. If you're familiar with Rust, this idea is somewhat similar to Rust's RefCell type, which lets you defer borrow checking to the runtime, at the cost of potentially triggering a panic if you try to mutably borrow the cell's contents when another borrow already exists. You can also find some backstory on the idea Inko uses from this 2006 paper (mirrored by Inko as the original source is no longer available): https://inko-lang.org/papers/ownership.pdf https://inko-lang.org/papers/ownership.pdf
- karmakaze 3y ago> Inko uses lightweight processes for concurrency, and its concurrency model is inspired by Erlang and Pony. Processes are isolated from each other and communicate by sending messages. Processes and messages are defined as classes and methods, and the compiler type-checks these to ensure correctness. > The compiler ensures that data sent between processes is unique, meaning there are no outside references to the data. This removes the need for (deep) copying data, and makes data races impossible. Inko also supports multi-producer multi-consumer channels, allowing processes to communicate with each other without needing explicit references to each other. Now I'm very interested. I was wondering why with 'actors' we'd still mark functions as async. It seems that a class marked async is analogous to an actor. class async Counter { let @value: Int fn async mut increment { @value += 1 } fn async send_to(channel: Channel[Int]) { channel.send(@value) } }
- YorickPeterse 3y agoA class marked as "async" can still have regular methods, which are only available to the inside of the class (i.e. the process itself, not other processes sending messages to it). So you can do something like this: class async SomeProcess { fn async some_message(...) { ... some_helper_method } fn some_helper_method { ... } } SomeProcess {}.some_message # This is fine SomeProcess {}.some_message # This is a compile-time error I toyed with the idea of _not_ allowing regular method and thus implicitly making all of them async for an async class, but that might lead to a pattern of async classes being "mirrored" by non-async "helper" classes just to reuse some methods between the different messages of an async class, i.e. you end up with this: class async SomeProcess { fn some_message(...) { SomeProcessHelper {}.some_random_method } } class SomeProcessHelper { fn some_random_method { ... } }
- karmakaze 3y agoGlad to see you here. Thanks that all makes sense, as making the async class's method async by default would then needs another keyword to make one non-async. Is the mut marking also for consistency (and/or non-message methods)? Presumably each actor/object would only process one message at a time so could always have full access to all its data. One more question, is memory allocated globally or from arenas/slabs/etc?
- deleted 3y ago[deleted]
- api 3y agoSeems like a very stripped down Rust.
- tedunangst 3y agoThis looks enough like rust that I'm not sure why I'd pick it over rust. What would be some concrete programs that would be difficult or tedious to write in rust and easy in inko? Could really use a section on "here's rust code you'd like to write but compiler won't let you" and how inko works.
- alex_lav 3y agoYou’ll note lifetimes aren’t a thing here, which is enough for me to give it a shot.
- singingboyo 3y agoApparently it's just moving that to runtime, which is kind of terrifying and misses the point. "If the owned value is dropped but references to it still exist, a panic is produced and the program is aborted; protecting you against use-after-free errors." [1] That "protection" seems pretty worthless to me, since a non-trivial number of the use-after-free bugs I've seen are triggered only in rare cases, which means you're still crashing in prod. Overall, Rust's lifetimes are sometimes hard, but generally only when memory safety is also hard. Inko's docs claim it makes it easier to implement self-referential data structures without unsafe/raw pointers, but to be honest the references here don't seem significantly safer than raw pointers. [1] https://docs.inko-lang.org/manual/latest/getting-started/memory-management/#references-and-borrowing https://docs.inko-lang.org/manual/latest/getting-started/mem...
- alex_lav 3y agoSo use Rust. I’m not understanding these complaints.
- pzmarzly 3y agoHow many times did you write `for<'a>` keyword or manually used `Pin<...>` type, or specified `where 'a: 'b`? I personally didn't need to reach for this "dark art" in at least last 2 years - thanks to Rust compiler developers making lifetime inference much better compared to how it's been few years ago. But there are still some warts around e.g. generic async functions, where you do sometimes need to think very hard about lifetimes and know plenty of tricks. Arguably those are situations where memory management is hard, but it's not something that e.g. TypeScript, C#, Swift or Scala would make you think about.
- pvelagal 3y agoInko means “one more” in Telugu, a language spoken in South India (Satya Nadella’s mother tongue). So its one more programming language!
- juunpp 3y agoHow is Saty even relevant here?
- macintux 3y agoRelated discussion: https://news.ycombinator.com/item?id=38261982 https://news.ycombinator.com/item?id=38261982
- dang 3y agoRelated. Others? Show HN: Inko 0.10.0 – build concurrent software with confidence - https://news.ycombinator.com/item?id=32811621 https://news.ycombinator.com/item?id=32811621 - Sept 2022 (3 comments) Inko 0.5.0 released, featuring the first steps towards a self-hosting compiler - https://news.ycombinator.com/item?id=20988908 https://news.ycombinator.com/item?id=20988908 - Sept 2019 (7 comments) Inko (a gradually-typed object-oriented programming language) 0.4.0 released - https://news.ycombinator.com/item?id=19893035 https://news.ycombinator.com/item?id=19893035 - May 2019 (1 comment) Show HN: Inko – A safe and concurrent object-oriented programming language - https://news.ycombinator.com/item?id=17702237 https://news.ycombinator.com/item?id=17702237 - Aug 2018 (45 comments)
- alecthomas 3y agoAre there binaries for ivm? If ivm is intended to make it easier to manage inko versions, having to install it via another language's toolchain (Rust) seems a bit onerous. Edit: Oh I see, ivm itself relies on Cargo to build inko rather than downloading binaries. Still, it would be nice if it didn't need another language's toolchain just to use Inko :(
- YorickPeterse 3y agoThere are no pre-built binaries. If you're using Arch Linux there's an AUR package (see https://docs.inko-lang.org/manual/main/getting-started/ivm/#arch-linux https://docs.inko-lang.org/manual/main/getting-started/ivm/#...), but for other platforms you'll need to use `cargo install`. I'm hopeful that over time the list of package managers including ivm will grow, making this process easier.
- pmontra 3y agoAn advantage over Erlang and Elixir is that this looks like any language from the C/Java family including all the clutter as in import std.fs.file.ReadOnlyFile import std.stdio.STDOUT class async Main { fn async main { We basically have a zero signal to noise ratio here but it's a declarative pattern common to many successful languages so it will make many people feel at home. I think it's a good design decision. On the other side I feel like the naming decisions in the error handling section are weird. The expect method breaks my expectations: let file = ReadOnlyFile .new('README.md') .expect("the file doesn't exist") file .read_all(bytes) .expect('failed to read the file') Well, no: I expect the file to exist and to be able to read from it. An expect method is common in test libraries and it behaves like an assertion. When I see an expect method I expect it to have a condition and stop the program if it evaluates to false, or propagate the error in any way appropriate to the language. So I expected let file = ReadOnlyFile .new('README.md') .fail("no_ro", "the file exists but it is not read only") .expect("created") .expect("open") I intentionally changed the probable behavior of the class, that seems not to create a read only file but only to open it. I made it create the file to show a softer version of expect() than an assertion. At least one of them must be true. The implementation of how to chain those methods without failing at the first failed expect() is a detail. And about .unwrap_or(0), what that even means? The linked post [1] hints about a "wrapper type,” like Maybe<T> so it's an established naming convention in some language but if it works as I think, what about this? .default(0) That because unwrap_or seems to ignore errors and return a default. If it doesn't, it confirms that it's a bad name. [1] https://joeduffyblog.com/2016/02/07/the-error-model/ https://joeduffyblog.com/2016/02/07/the-error-model/
- filmor 3y ago`unwrap_or` and `expect` work exactly like they do in Rust. The naming of `expect` works nicely if the argument is the precondition that failed (see https://doc.rust-lang.org/std/error/index.html#common-message-styles https://doc.rust-lang.org/std/error/index.html#common-messag...), e.g. .expect("should have opened file")
- pmontra 3y ago
- sudarshnachakra 3y agoI had been only following this language with some interest, I guess this was born in gitlab not sure if the creator(s) still work there. This is what I'd have wanted golang to be (albeit with GC when you do not have clear lifetimes). But how would you differentiate yourself from https://gleam.run https://gleam.run which can leverage the OTP, I'd be more interested if we can adapt Gleam to graalvm isolates so we can leverage the JVM ecosystem.
- YorickPeterse 3y agoWhile I indeed worked for GitLab until 2021, Inko's development began before I joined GitLab, and GitLab wasn't involved in it at all. Inko was hosted on GitLab for a while (using their FOSS sponsorship plan), but I moved it back to GitHub as to make contributing easier and to increase visibility. As much as I prefer GitLab, the sad reality is that by hosting a project there you'll make it a lot harder for people to report bugs, submit patches, or even just find out about your project in the first place. I could go on for a long time how GitLab really wasted its chances with taking over GitHub, but that's for another time :)
- toastal 3y ago> To add a package, first create a GitHub repository for your package. While Inko's package manager supports the use of any Git repository (e.g. one hosted on GitLab), the above list is populated using GitHub repositories only. So another community that wants to lock itself into Microsoft products as well as Git in general. Why should a user’s choice for DVCS & forge make them feel less supported or inferior?
- fshr 3y agoBecause PL devs and their communities, until very large, have limited resources; supporting niche use cases needs to be provided by people wanting that niche served, not by the primary contributors.
- toastal 3y ago…Or use something generic like a tarball? Almost all DVCS have the ability to export an archive. Don’t build on lock-in; build on the generic thing… and then provide an ‘enhanced’ experience for something specific like Git or a specific forge.
- alexvitkov 3y agoWhen did the ability to clone a repo outside of GitHub become a niche use case? It's not something you need to explicitly support, in fact you have to actively go out of your way to make sure only GitHub works.
- YorickPeterse 3y agoInko's package manager supports non-GitHub repositories just fine. What the original comment likely is referring to is the website only including GitHub based packages. This is simply because the script that populates that list hasn't been extended to support more than GitHub, because there hasn't been a need for it so far.
- 112233 3y agoIt is not even the worst I have seen. E.g. Kinesis and others sell keyboads with ZMK open source firmware, where official way to change key map is making github repository with a fork, editing it and using github to build it. I wish I was making this up. So yes, seems like github has joined twitter and whatsapp as another thing the world works on.
- matrix2596 3y agofunny in my language inko (telugu) means another. so its yet another programming language.
- rjh29 3y agoInko is Japanese for 'parrot'. Although it can also mean 'obscenity' which came to mind...
- pzmarzly 3y agoGiven the big similarity to Rust, and having similar target group (people who care about concurrency in compiled languages), what made you decide to stray from Rust syntax @YorickPeterse ? Personal preference? It's all about small stuff like: `async fn` (Rust) vs `fn async` (Inko), skipping parens for 0-arity functions in Inko, `::` vs `.`, `Generic<T>` vs `Generic[T]`.
- simon_o 3y agoMost likely not wanting to repeat Rust's mistakes.
- avgcorrection 3y ago`<T>` in Rust is due to C++ pandering (assuming that C++ like it—unsure). I don’t see why “people who care about concurrency in compiled languages” should care about C++ syntax (which is both ugly to read and hard for computers to parse).
- kazinator 3y agofn async means that by looking at the first token, we already know we are parsing a fn. When you see an async, you only know you're parsing a fn while fn is the only thing in the language that can be async.
- YorickPeterse 3y agoI have mixed feelings on Rust's syntax, especially around generics, lifetimes, and the `modifier -> keyword` syntax (i.e. `async fn` or `pub fn`). For Inko, I wanted something that's easy to parse by hand, and no context specific parsing (e.g. `QUOTE -> something` being the start of a lifetime in one place, but a char literal in another place). Another motivator for that is that years ago I worked on Rubinius for a while (an implementation of Ruby), and helped out with a parser for Ruby (https://github.com/whitequark/parser https://github.com/whitequark/parser). The Ruby developers really liked changing their already impossible syntax in even more impossible ways on a regular basis, making it a real challenge to provide syntax related tools that support multiple Ruby versions. I wanted to avoid making the same mistake with Inko, hence I'm actively trying to keep the syntax as simple as is reasonable. As for the specific examples: `fn async` means your parser only needs to look for `A | B | fn` in a certain scope, instead of `A | B | fn | async fn`. This cuts down the amount of repetition in the parser. An example is found at https://github.com/inko-lang/inko/blob/8f5ad1e56756fe00325a3b4ed0fc3c46e1d50e65/ast/src/parser.rs#L1152-L1169 https://github.com/inko-lang/inko/blob/8f5ad1e56756fe00325a3..., which parses the body of a class definition. Skipping parentheses is directly lifted from Ruby, because I really like it. Older versions took this further by also letting you write `function arg1 arg2`, but I got rid of that to make parsing easier. It's especially nice so you can do things like `if foo.bar.baz? { ... }` instead of `if foo().bar().baz?()`, though I suspect opinions will differ on this :) Until recently we did in fact use `::` as a namespace separator, but I changed that to `.` to keep things consistent with the call syntax, and because it removes the need for remembering "Oh for namespaces I need to use ::, but for calls .". `[T]` for generics is because most editors automatically insert a closing `]` if you type `[`, but not when you type `<`. If they do, then trying to write `10<20` is annoying because you'd end up with `10<>20`. I also just like the way it looks more. The usual ambiguity issues surrounding `<>` (e.g. what leads to `foo::<T>()` in Rust) doesn't apply to Inko, because we don't allow generics in expressions (i.e. `Array[Int].with_capacity(42)` isn't valid syntax) in the first place.
- librasteve 3y agoSadly another language with concurrency support that fails to learn the lessons from occam https://en.wikipedia.org/wiki/Occam_(programming_language) https://en.wikipedia.org/wiki/Occam_(programming_language). Erlang, Go, Rust, Raku and now Inko all missed the point: 1. synchronous channels guarantee correctness 2. so the same code can be run concurrently (ie lightweight processes on one CPU) or in parallel mapped to multiple non shared memory CPUs
- icholy 3y agoGo channels are synchronous by default.
- librasteve 3y agoyes - but ... in the occam model that is not safe so it is not allowed
- raiph 3y ago> Sadly another language with concurrency support that fails to learn the lessons from occam. Weren't the top two lessons learned from occam about indeterminacy?: 1. If you ignore indeterminacy, you haven't really tackled concurrency, so you become irrelevant. CSP 1 ignored it, so occam 1 and 2 were designed to ignore it. While they became irrelevant for several reasons, I've long thought it was the fatal mistake of ignoring indeterminacy that made the demise of the original occam series inevitable. 2. If you tackle indeterminacy, but not its deeper consequences, you remain irrelevant. occam-π, which has added indeterminacy constructs as an after thought, has the problem that it's tackled it from the outside in. During the 2019 concurrency talk panel that brought together three concurrency giants -- the late Joe Armstrong (Erlang), Sir Tony Hoare (CSP / occam), and the late Carl Hewitt (Actor Model) -- Tony said: > The test for capturing the essence of concurrency is that you can use the same language for design of hardware and software, because the interface between those will become fluid. We've got to have a hierarchical design philosophy in which you can program each individual ten nanoseconds at the same time as you program over a 10 year time. And sequentiality and concurrency enter in to both those scales. So bridging the scale of granularity and of time and space is what every application has to do. The language can help them do that and that's a real criterion to designing the language. Both Joe and Carl instantly agreed about what Tony said but also instantly disagreed about the central role of indeterminacy, and one could be forgiven for thinking Tony still hasn't learned the lesson of the mistake he made with CSP 1 and occam 1. Erlang's "let it crash" concept distilled Joe's fundamental understanding of the nature of the physical universe, and how to write correct code given the inescapable uncertainty principle aka inescapable indeterminacy. The Actor Model, which is a simple, purely mathematical model of purely physical computation, ironically the right theoretical grounding if you apply "Occam's Razor" to concurrent computation in our physical universe, contrasts sharply with the more "airy fairy" process calculi, which abstract processes as if one can truly ignore that, for such calculi to be useful in reality, the processes they describe must occur in reality -- and then indeterminacy becomes the key characteristic to confront, not an afterthought. At least, that's my understanding. I recall loving occam when I first read about it in the mid 1980s, partly because I was writing BCPL for a living at the time, and occam's syntax was based on BCPLs, but also because I was getting interested in concurrency, and fell in love with the Transputer, which was created by Inmos, a Bristol UK outfit, and I lived in the UK having spent a couple years living a few miles from Bristol. But one can't deny reality, and the laws of physics, and the growing complexity of software, and the consequences of those two fundamentals, so eventually I concluded the Actor Model was going to outlive CSP and occam. I still think that, but am ever open to being persuaded of the error of my ways of thinking...
- arp242 3y agoHaving pre-built binaries would be hugely helpful; I can't get it to compile (can't "find" LLVM, even though it's installed and everything should be set up right – doesn't help that llvm-sys error message isn't very helpful in explaining what went wrong). I have no doubt I could get it to work if I spent more time on it, but ... I have no idea of that's going to be worth the effort, because I don't know if Inko is even interesting for me, as I can't actually try.
- deleted 3y ago[deleted]