ljackman
The problem with Kotlin and the like is that they can't easily compile away their features due to inherent runtime dependencies, e.g. garbage collection, making them poorly suited to environments with a very minimal runtime like WASM, while also being at the mercy of the host language creating runtime abstractions that have mismatches with their own language's features.
Although it'd be unfair for me to say the JVM is designed only for Java; invokedynamic and non-reified generics both assist JVM targetting for non-Java languages such as Clojure.
However, for application languages that can afford a few extra nicities like garbage collection, I fail to understand why the stackless coroutine model (`suspend` in Kotlin) or `async`/`await` continue to be the developer's choice. Why do languages like Kotlin adopt these features, specifically?
Manually deciding where to yield in order to avoid blocking a kernel thread seems outside of the domain of problems that those using a _higher level_ language want to solve, surely?
The caller should decide whether to do something "in the background". And this applies to non-IO capabilities too, as sometimes pure computations are also expensive enough to warrant not blocking the current task.
Go and Erlang seem to have nailed this, so I'm glad Java is following in their footsteps rather than the more questionnable strategy of C# and Kotlin. (Lua's coroutines and Scheme's `call-with-current-continuation` deserve an honourable mention too.)
What about keeping the network allowed in the layer handling, say, inbound HTTP connections, but blocking it in the data access layer or purely computational component?
From what I can see, this doesn't work with global boolean flags in the runtime, instead requiring isolated tasks with whitelisted capabilities passed in, some form of "immutable, set-once, dynamically-scoped capability flags", or something like that.
The problem with the global boolean flag approach is that if any part of a service needs it constantly, the entire program gets it, even obscure subdependencies for generating colour pickers.
Don't get me wrong, it's an incremental improvement over's Node.js blase approach. It's also quite niche to see languages support this feature. E was one of them. There was another newer Python-like language with this too, starting with an `M`, but its name escapes me.
I'd recommend Deno's developers look at E a bit more before committing too much to the platform boolean flag approach. Or I've misunderstood their approach and it actually does more than I'm giving it credit for.
My brief skimming of its site indicates that its security model is based around the ability to disable, say, network access for whole Deno programs. However, does it allow starting up with network access, allowing a subset of the program to handle it, and then dropping those rights for the rest of the program, _especially_ subdependencies?
I can't see any mention of a more fine-grained approach: https://deno.land/manual/getting_started/permissions
A modern Node.js web service will have hundreds, if not thousands, of indirect dependencies. Some network access will be required for at least the Express routing, or an equivalent.
For a Deno equivalent, this would amount to enabling network access for all hundreds of those subdependencies, not reproducing the isolation in capability-based security such as WebAssembly nanoprocesses.
Have I missed something here? That doesn't seem like much of an improvement over Node.js except for very small and contained programs. Yes, Deno's dropping of centralised package repositories and package.json might alleviate this problem _somewhat_, but the same fundamental issue seems to remain.
Java will have a similar problem with CompletableFuture and third-party projects like Reactor used in Spring WebFlux.
However, Kotlin baking in language syntax for a competing concurrency model will exacerbate the split.
With Java already having type inference, concise lambdas, and with upcoming records that can already be faked in current versions via Lombok, the value proposition of Kotlin seems questionable outside of Android with its held-back version of Java.
Clojure is another story entirely, offering a very different and compelling way of thinking about computing on the JVM. By contrast, my developing view is that Kotlin is different enough to require learning something new yet not different enough from modern vanilla java to offer substantial advantages.
The biggest impact of the 5 years for working developers has probably been:
* Streaming API and lambdas allowing usual `map`/`filter`/`reduce` transformation pipelines line many other languages (lazy, unlike JS's `Array` methods, parallelism considered via "spliterators" unlike Python).
* Default methods in interfaces, _not_ making them traits, but arguably more "trait-like". It allowed them to add many useful methods onto existing types without needing bifurcate the common APIs.
* A REPL. Probably not a big deal for a professional developer, who likely uses IntelliJ's "Evaluate Expression" feature already for a similar feature, but pretty useful for education I guess.
* Better at being stuffed inside Docker containers with resource limits. Also, max permgen space no longer a problem really (it's where you had to set an upper memory limit when invoking Java programs, which was annoying). Just generally less annoying at being deployed in modern infrasructures.
* APIs like `File.lines` that mean you don't need to recite War and Peace just to do a buffered operation across lines in a file. Generally more ergonomic APIs that finally make basic operations sane.
* Type inference with `var`. Basically the same as C#'s `var` or Go's `:=`.
* More native executable-producing tooling. jlink, jpackager, etc. The dream of ubiquitous, shared Java runtimes installed everywhere didn't really work out, and they seem to have realised that.
* Ecosystem moving away from XML. Still there, just less common. Runtime annotations are used more heavily, which I'm honestly not sure is a massive improvement. Hopefully more lambda-heavy APIs will reduce use of runtime annotations; see Spring WebFlux's functional handlers for annotation-based controllers for a comparison.
There's a lot more, but I'd argue it won't be as immediately visible for the working developer more than those points. Modules were important for the ecosystem, but most devs probably aren't worrying about that day-to-day. Gradle and Maven still dominate the building/packaging side.
Did it drop the idea of assembly-wide opt-ins to stricter behaviour, meaning all NRTs can be reasoned about in the same way without considering a configuration flag somewhere like PHP? That does sound like an improvement.
Doing the change gradually, without breaking existing code or requiring potentially-ecosystem-breaking opt-ins does seem eminantly sensible and user-friendly. I was being too harsh to C# here. Java's `Optional` doesn't even warn about it itself being null, for example. You need static tooling and code analysis for that. C#'s solution does at least try to solve that, albeit at the cost of more complexity.
I said "monad-like" rather than "monadic" for that reason, but arguably it doesn't even go far enough to be considered monadic-like. Certainly this article would agree: https://blog.developer.atlassian.com/optional-broken/
"Infinitely" more useful seems like a stretch. Both languages made valid design decisions with their lambdas: autogenerated types means not every variant of lambda needs a backing interface, but it also means that the types are a world unto themselves and not integrated with well-established interfaces and abstract classes in the way that Java lambdas are, resulting in conflicting mechanisms for passing code to adhere to a requirement. I find the unification of SAM types and lambdas to be elegant in a class-based OOP language (and actually preferable to the Smalltalk/Ruby block model too), but it's clearly subjective.
My criticism of properties hiding side-effects as attribute reads is mostly derived from one of the earliest books about the CLR; was it "The CLR via C#"? I'll have to check. The critique isn't Java-inspired; getters have the same problem of course. The point is that a getter is a method call, so you expect potential side-effects. You don't expect side-effects from a property read, although C# does tend to use capital letters for properties, to be fair.
While on the topic of CLR, I realise the DLR exposed dynamic typing primarily to make the CLR a better target for dynamic languages; I was arguing that exposing that up to C# wasn't necessary. C# is the flagship CLR language, sure, but that doesn't mean it must expose _every_ feature of it. Java also added `invokedynamic` for similar reasons but didn't feel the need to expose it to its flagship language directly in language syntax.
pron covered the nuances around generic type erasure in another comment better than I did, so I won't reiterate. Like you, I still mildly prefer reified generics over erasure as a language user, but the points raised by pron and Bracha are absolutely real. The ability to do runtime type checks and default values on generics seem like antipatterns, so I'm glad Java doesn't support those _specific_ features of reification even if I like a lot of the others parts.
* Too many overlapping concepts for referring to code by value and defining them inline: events, delegates, anonymous delegates, and lambdas.
* Lambdas that generate magic types rather than slotting into SAM types. This works great for functional languages, sure, but doesn't fit well into a class-based OOP language.
* `ref` and `out` parameters to appease archaic COM APIs.
* Tuple unpacking, which makes sense independently but then bizzarely tries to integrate with `out` parameters.
* A nice "ex nihlo" object literal syntax that shoots itself in the foot by undermining immutability due to requiring settable fields. (TBF, later versions fixed this IIRC.)
* Inline functions in methods in a language that already has lambdas and methods; it's just bloat.
* `as` casting that yields nulls. Did it really need a whole new syntax just to handle null more concisely specifically for casting?
* `partial` classes. Encouraging even more code generation with features like this is a questionable idea and wasn't necessary in other languages.
* `dynamic`. Even if there are use cases for it, it's strange in an otherwise static language with a top-level Object type anyway. I'm saying this as someone who's perfectly happy with fully dynamic languages like Erlang and Lisp. I just don't see the point of adding it to C# specifically.
* C-style enumerations.
* Properties. Invoking side-effects on something that is syntactically indistinguishable from an attribute read is a bad idea. Auto-generation of Java's verbose getters would be long overdue, but the caller site shouldn't be the same a la C#.
* Extension methods. It seems quite ad hoc compared to static addition of types to common operations in other languages, like imported traits in Rust or typeclasses in Haskell.
* Proposed "shapes". Looks like a good idea by itself but will overlap too much with default method implementation and other existing mechanisms.
* Interfaces prefixed with `I`. Not strictly a language problem but an ecosystem one. I shouldn't need to know what _sort_ of type I'm dealing with, that's '90s Hungarian notation.
* Nullable reference types. Getting rid of null is good, but this proposal became confusing. They mentioned opting in assembly-wide for a while but there was then a conversation about having it just warn in some cases. I need to read the latest literature around this, but it seemed less elegant than Java just adding a monad-like Optional type and not adding loads of special-case operations with question marks everywhere.
Despite this, I still admire a lot of the design decisions behind C#. LINQ was great. `async/await`, despite my belief of its inferiority to Goroutines/Project Loom/Erlang processes, was still a great innovation from Midiori at the time. Value types were obviously right to be implemented early on. Assemblies were a good idea. Private by default rather than package-accessibility was a nice touch, as was the `override` keyword. The C# team are smart people who know what they are doing!
As an aside, I used to be firmly against erased generics, but reading more about the tradeoffs from the likes of Gilad Bracha has caused me to reconsider.
That isn't to say the designers of JavaScript and C# don't think carefully about the addition of new features; indeed, was it Anders Heljsberg who made the point about all new proposed features starting with negative points?
But Java's recent and upcoming additions show _taste_: adopting a single lambda notation that fitted smoothly with the surrounding class-based OOP paradigm via SAM types (rather than magically-generated-type-style lambdas succeeding two overlapping "delegate"/"event"-like features); implementing (sadly somewhat-incompatible) modules that can curtail unbounded reflection, opening the door to greater reasonability and performance in the future; proposing Project Loom to avoid the async/sync API split that hit Python, C#, and even Kotlin Coroutines; and now Valhalla, which was quietly mulled on for years before arriving at this very reasonable solution that considers myriad angles.
I like this approach to language design and think it bodes well for the language's future. It's a sweet spot between being necessarily conservative, dealing with developers' real-world problems, and giving time to mull over new language feature designs and not just implementing as soon (and haphazardly) as possible to please vocal developers.
This bug reminded me a bit of Heartbleed actually; it’s pretty different but has that one similarity of two payload lengths, one reported and one actual, disagreeing and not being reconciled, causing memory bugs.
Even with memory safe languages, when you go beyond basic serialisation and look at libraries like Jackson for Java, you discover _very_ questionable designs such as providing an API that makes it all too convenient to switch on a feature which allows user-provided strings to control which classes to instantiate. They solve this using a blacklist of “known bad classes”, which seems to be a workaround rather than acknowledging that many features of common serialisation libraries are a fully loaded uzi pointing at the developer’s foot.
It seems common implementations of serialisation and deserialisation have prioritised ease of use for the developer over declaring clear, bounded intent, and this seems to apply from ad hoc C parsing of network payloads all the way up to automatic serialisation in memory-safe languages.
Non-techies take a lot of the core infrastructure and tooling for granted, not realising just what a heap of technical debt and accrued complexity it all is, and therefore just how hard it is to keep secure, despite the large security budgets and talents of the big FAANG companies.
It’s only by these lower levels being attacked and yielding bad PR for their parent companies will we eventually see less of a focus on new features for core ecosystem platforms and more of a focus on reducing the technical complexity and improving the security of what we have.
This will however need to be combined with technology journalism that’s more focussed on putting such vulnerabilities into accessible stories for the layperson and less focussed on being the unofficial marketing wing of technology companies on announcements.
My phrasing was probably a bit ambiguous there.
It seems that interprocedure optimisations in modern compilers make a lot of const-by-reference optimisations apply even when the code is mutable-by-reference in the parameter list but the function body doesn't modify it in practice. This would only work if it could deterministically work out which function is called.
Local constant stack values surely can be completely deterministically verified as such by the optimiser even without the `const` modifier. They could be overwritten without a C assignment to it, via the stack from a buffer overflow of an array next to it, but that's undefined behaviour so a compiler is presumably free to assume it is not modified, eliminate unnecessary register/memory loading code, and let the developers deal with the consequences.
As trailing `const` on member functions outlaws modifications via `this`, it would follow that the same optimisation-even-without-modifier process would apply as to `const` local stack values.
As constancy is a constraint that aids human comprehension, there's a good reason for choosing a keyword just as short as the mutable equivalent, such as Swift's `let` vs `var`; if the more constrained equivalent is equally or more convenient, more constrained and thus easy-to-reason-about code becomes more common.
I originally felt it was overly complex, but after seeing some of the Go text/template and Ansible Jinja examples in the wild, it actually seems like a good idea.
Perhaps we should more strongly distinguish between “basic” data definition formats and ones that need to be templated. JSON5 for the former and Jsonnet for the latter, for example.
* Broken base Ubuntu images being recommended by Atlassian as the default for agent Image configuration, only to be fixed to a usable state months later;
* Being generally years behind other CI tools, even the traditional ones;
* Data exports corrupting themselves despite apparently succeeding, blocking migrations after upgrades or server changes;
* The official documentation somewhere recommending copying across password hashes directly for setting up new admin for a migration, but I can't find this anymore so they've hopefully improved the process and documentation for this;
* A bug in an earlier version in which a strange combination of error cases in an Elastic Bamboo image configuration could spam EC2 instances in a constant loop, which we thankfully spotted before it ate through our AWS bill;
* No clear messaging from Atlassian about how the future of Bamboo compares to Pipelines. The official line seems to be that Bamboo is for highly custom setups who want to host their CI themselves, but you get the impression from to the slow pace of development that they're slowly dropping it in favour of Pipelines. I'd rather they be honest about it if that is the case.
Those are just the problems I can think of from the top of my head, anyway.
However, it doesn't seem to be _entirely_ an implementation detail. Some developers just don't seem to like the semantics of called functions being able to cooperatively yield without the caller explicitly opting into it with a keyword like `await`. I disagree with them, but it's a legitimate complaint I've a few times.
It reminds me of arguments in the Lisp community about delimited continuations and undelimited, i.e. Common Lisp and Scheme. A lot of the arguments there are really about semantics and not implementation details, and come to the same point: should cooperative scheduling require explicit notation at each level of the call stack?
My view on this is that systems languages like C and Rust should require explicit notation for it whereas application languages should not. This seems to be a point in favour of Go and Erlang over Java and C#.
However WASM, similar to C or Rust, seems to target a level in the tech stack at which it should concern itself only with abstractions that have relatively direct translations to the instruction set of the underlying hardware. Support for multiple stacks doesn't fit into this from what I can see. (A similar argument can be made for WASM not supporting garbage collection too, although it looks like that'll be added at some point to make interoperability with JS smoother.)
With the JVM supposedly adding fibres soon, it poses a question for WASM: is it trying to be a portable assembly language, a portable high-level language runtime, or something in between?
Undelimited "asymmetric" coroutines, like Lua's, could be an interesting addition. That still seems to me to be too high level a feature for a "portable assembly language" specification though.
In particular, I like that it kept it simple by either an application being sandboxed with explicit user permissions for certain features, or not being sandboxed at all. Contrast with Linux FlatPaks where even "sandboxed" programs can have a wide range of implicit permissions based on the image configuration, which aren't obvious to end users when running `flatpak install` in the same way that macOS's privacy settings are for each sandboxed macOS app.
I remember some FlatPak applications defaulting to allowing unsandboxed home directory access without prompting me on installation, which seemed to defeat the purpose somewhat. It does block nefarious control flow attacks against programs that declare a strict sandbox, but that doesn't seem to go far enough in my view.
A DOMain of Shadows
Reader macros in CL allow it to implement much of Clojure, but Clojure's lack of them means it could never implement CL unless it implemented built-in readers to handle every possible reader macro that a CL implementation loaded in.
As arbitrary library code can access the reader in CL, this would apparently be impossible.
I suppose it comes down to whether a Lisp dialect wants to support arbitrarily-powerful metalinguistic abstractions in its own Lisp syntax or provide the ability to create whole new languages with different fundamental tokens.
If a dialect only wants to achieve the former but has no interest in the latter, AST macros would suffice and custom reader macros would be of little interest, which is presumably the reasoning behind Clojure's lack of them.
Anyway, kudos for a great project.
Unfortunately, those restrictions also make mobile OSes less useful for the sort of technical work that people use "real" OSes for. However, those restrictions make such devices much more compelling roots of trust for our digital lives.
An iOS app can't extract my contacts unless I grant permission, yet an application installed on Ubuntu via `apt` can casually start rummaging around my home directory which I won't know about unless I spend considerable time on mandatory access control profiles, isolation through containerisation or virtualisation, or something equally esoteric for the average user.
I suppose a phone does hold more sensitive information though, like location and mobile payments, making it a more lucrative target.