5 ms·
I can give perspective as someone who enjoys modern Java, writes Kotlin at their dayjob (and loves it), and also likes Scala 3. Here are the things that if Jav
by gavinray 3y ago
I can give perspective as someone who enjoys modern Java, writes Kotlin at their dayjob (and loves it), and also likes Scala 3.
Here are the things that if Java had, I probably wouldn't see a reason for other languages:
1. Lack of first-class lambda syntax. In Kotlin/Scala you can write something like:
fun doSomething(handler: (String, Int) -> Foo): Blah
In Java, all you have are the "Function<>" and related interfaces, which are clunky to use.
2. Opaque types (Scala 3). These have been one of the most impactful programming features I've ever used, and I sorely miss them in languages that lack them.
object Foo:
opaque type UserId = Int
opaque type Email = String
def mkEmail(s: String): Email = s
def findUserIdByEmail(email: Email): UserId = 42
import Foo.*
val email: Email = mkEmail("foo")
val valid: UserId = findUserIdByEmail(email)
val invalid1: UserId = findUserIdByEmail("bar") // error: can't use String as Email
val invalid2: UserId = 123 // error: can't use Int as UserId
3. Union types (Scala 3). You can emulate them in Java/Kotlin with Sealed Types but it's much more verbose.
type JsonScalar = String | Int | Boolean
type Json = JsonScalar | Map[String, JsonScalar] | List[JsonScalar]
// Makes writing functions that take multiple argument types much easier:
def handle(it: String | Int | Boolean): Unit = ...
4. Context-oriented programming with "given/using" in Scala 3 and "context-receivers" in Kotlin.
This one is harder to explain succinctly but essentially it allows you to decorate methods/classes with required "contextual" args.
Instead of passing them as regular function arguments, you must invoke the function inside of an "environment"/"context" where the requirements are satisfied.
This makes threading dependencies through your code much easier, and eliminates the need for dependency injection frameworks in many cases.
interface Logger {
fun log(message: String)
}
object ConsoleLogger : Logger {
override fun log(message: String) = println(message)
}
ctx(Logger)
fun doSomething(): Int {
log("Hello")
return 42
}
fun main() {
val logger = ConsoleLogger
with (logger) {
doSomething()
}
}
5. First-class support for asynchronous programming. With "suspend" in Kotlin and a current prototype being done in Scala 3:
- REPO: https://github.com/lampepfl/async https://github.com/lampepfl/async | SLIDES: https://github.com/lampepfl/async/blob/main/scalar-slides.pdf https://github.com/lampepfl/async/blob/main/scalar-slides.pd... | YOUTUBE TALK: https://www.youtube.com/watch?v=0Fm0y4K4YO8 https://www.youtube.com/watch?v=0Fm0y4K4YO8
6. Passing arguments by name, rather than positionally.
fun doSomething(a: Int, b: Int, c: Int): Int = ...
doSomething(a = 1, b = 2, c = 3)
7. Tuples (Scala). They're like anonymous data classes/records.
val t: (Int, String) = (1, "foo")
val (a, b) = t
- tejinderss 3y agoHow about typescript? It supports union types, lambda functions and type aliases. Would you consider it on par with Kotlin?
- gavinray 3y agoI adore TypeScript -- JS/TS has been my primary language for most of my career. Kotlin has a few things that TS doesn't have but overall, I'd rate it very similarly. I probably enjoy writing TS a bit more than Kotlin. EDIT: TS also has Opaque Types, sort of. They call them "brand types" or other names, it looks like this: declare const opaque: unique symbol declare type Opaque<T, K extends string> = T & { readonly [opaque]: K } type Email = Opaque<string, 'Email'> function mkEmail(email: string): Email { return email as Email } function takesEmail(email: Email) { console.log(email) } const email = mkEmail('foo@bar.com'); takesEmail(email); takesEmail('wrong'); // error
- tejinderss 3y agoYeah i think extension functions and operator overloading are biggest things in kotlin that are missing in typescript. Its very easy to add functions to existing types and chain the method calls in kotlin. Edit: didnt know about the brand types in typescript, TIL, thanks for the example.
- lenkite 3y ago1. Java already has succinct lambda expressions. parameter -> expression (parameter1, parameter2) -> expression (parameter1, parameter2) -> { code block } and function signatures like the below might be "clunky" from your point of view, but IMHO are more clear since they document explicit types. (and you can navigate to their javadoc) Blah doSomething(BiFunction<String, Integer, Foo> fn) 2. Conceded. There are some JEP's around this but they all got rejected. 3. Already covered in existing discussion. Verbosity level is fine. 4. Context oriented programming can easily be achieved using AOP in java. But frankly is readability and maintainability nightmare in any large project. I have seen projects/apps/libs that used this paradigm (in several languages) be re-written to explicitly designate all contexts. 5. Java with virtual threads now has far better support for async programming than Kotlin or Scala. It is now competitive with Golang in async ease-of-use. https://blog.rockthejvm.com/ultimate-guide-to-java-virtual-threads/ https://blog.rockthejvm.com/ultimate-guide-to-java-virtual-t... 6. shrug. Many popular languages have explicitly rejected function named parameters. Use a struct/record if you want named arguments is the usual answer.