4 ms·
Examples 3 and 4 are cursed, to my eye. Curly braces denote a block, so why is there some magic syntax inside the block that denotes that this block happens to
by kennywinker 5y ago
Examples 3 and 4 are cursed, to my eye. Curly braces denote a block, so why is there some magic syntax inside the block that denotes that this block happens to be a lambda? surely the block is the closure itself. The syntax to say what the block is goes outside.
Imagine if i wrote a conditional like that:
if x > y { return x; } else { return y; }
as
{ if x > y; return x; } { else; return y; }
It completely erases the usefulness of {}, and is cursed, cursed I say! And I’m looking at you, rust, swift, ruby, etc.
- ulrikrasmussen 5y agoI wholeheartedly agree. Whether {} denotes a block or a lambda is context-dependent in the language I took these examples from (Kotlin). I think they adopted the syntax from Groovy. It is "useful" for creating DSLs because you can implement constructs like `.forEach` so they look like imperative constructs, e.g. list.forEach { x -> ... } But it becomes harder to read code outside an IDE, especially if the implicit `it` construct is used.
- zorr 5y agoThat's the thing that annoys me most about Kotlin (even though aside from that it is my personal 10X language). In a large project that uses DSL's, coroutines and flow it quickly becomes a curly brace mess and it gets hard to see what code runs where, when and in which context.
- ulrikrasmussen 5y agoSame, I use Kotlin daily, and I would choose it over Java any day as it really solves a lot of its quirks very nicely. There are however some parts of the syntax that keep confusing me on a daily basis, curly braces being one of them. The other is the weird unintuitive asymmetry that is going on between function types and lambdas. For example, a lambda of type (Int, Float) -> Boolean is written as { i, f -> ... body ... } but a lambda of type (Pair<Int, Float>) -> Boolean is written as { (i, f) -> ... body ... } because `(x, y)` is a destructuring pattern that projects the first and second components into `x` and `y`, respectively. The pattern in the lambda has to use tuple syntax exactly when the type doesn't, and vice versa. Gets me on a weekly basis even though I completely understand what's going on, it's just not very ergonomic.
- RedNifre 5y agoNo, the idea is that {} denotes executable blocks, which lamdas are. In Kotlin, if the last parameter of a function is a lambda, you can close the parenthesis before the lambda: f(5, { square(it) } can be written as f(5) { square(it) } which makes constructs like if(condition) { foo() } Look like if(condition, { foo() } ) as in a function that takes a boolean and a lambda and only executes the lambda if the boolean is true. It's a neat reinterpretation of what {} means.
- kennywinker 5y agoI’m not super familiar with kotlin, but swift does the same thing you’re describing so i get where it’s useful. My beef is with including metadata about the executable block (parameter names) inside the block. So per your example: f(5) { square(it) } My preference would be to tag the fn signature on the outside of the braces, something like: f(5) [(x)->int]{ square(x) } I’d also accept ||, (), or nothing as the delimiters around the fn signature. Key point is that it’s OUTSIDE the executable block. I’m not opposed to using some smarts to infer/simplify the expression when possible. I.e. if it’s a closure with inferable parameter and return types the “it” construct could be used (in swift they use $0, $1, $2 etc for unnamed parameters). Just the only thing inside an executable block {} should be code that gets executed - not type information about that block