dart-lang / language

Design of the Dart language
Other
2.68k stars 205 forks source link

Allow more concise function literal argument #343

Open eernstg opened 5 years ago

eernstg commented 5 years ago

A Dart function invocation requires parentheses, e.g., f(42). Other languages can be more concise.

Functional languages like Haskell and SML use an "empty" operator (a space), e.g., f 42. With curried invocation this extends to several arguments (f 42 true), but curried function invocation would be a radical change for Dart, and simultaneous invocation with multiple arguments in functional languages still require parentheses, because we're passing one value which is a tuple (f (42, true)). This seems to indicate that Dart cannot easily use this approach.

However, some languages exploit special properties of the declaration (like being the last parameter) and the call site (like passing a function literal, rather than an arbitrary argument), and that allows the concise form using just a space.

For example, a Ruby function invocation can have an 'associated block' which is passed using a mere space (example from Ruby core Array doc):

arr = [1, 2, 3, 4, 5, 6]
arr.select {|a| a > 3}

In the callee, it's possible to test whether such a block was given (block_given?) and call it (yield), but here we are just interested in the fact that we a block is passed at the call site using a concise syntax.

Kotlin uses a similar syntax to pass a lambda as the last argument in a function invocation. Here is an example which is at the same time an example of a type-safe builder:

class HTML {
    fun body() { ... }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()
    html.init()
    return html
}

// Invoke `html`, using a mere space to pass the argument `{...}`.
html {
    body()
}

This issue is a request for a similar kind of conciseness in Dart. It may seem like a tiny thing, but it is probably going to be used in a large number of locations.

sczerniawski commented 5 years ago

[This is also re #344]

I'm surprised that nobody mentioned Haskell's $ operator.

f a b c   = (((f a) b) c)  -- f(a, b, c) in languages without currying like dart
f a $ b c = ((f a) (b c))  -- f(a, b(c))

I'm mentioning this only as an example of a syntactic structure that solves the ambiguity problem. $ is probably not a good token to use in dart.

munificent commented 5 years ago

but curried function invocation would be a radical change for Dart, and simultaneous invocation with multiple arguments in functional languages still require parentheses, because we're passing one value which is a tuple (f (42, true)). This seems to indicate that Dart cannot easily use this approach.

Yes. Ruby has optional parentheses and it gets really weird. To avoid some ambiguous cases, the language actually makes a space before ( significant in some places and produces an error when it's omitted. Consider:

sqrt (1 + 2).abs + 3;
sqrt(1 + 2).abs + 3;

I think making parentheses optional is profoundly confusing in a C-derived syntax.

Wdestroier commented 8 months ago

Could

numbers.map(it: it + 2)

be an improvement over

numbers.map((it) => it + 2)

?

EDIT: I talked to Randal Schwartz and he pointed out this syntax is ambiguous with named parameters.

Abion47 commented 5 months ago

I forget what language it was, but I remember seeing a lambda shorthand that, when there was only one parameter, it allowed forgoing the parameter list entirely by implicitly defining a val parameter (or something similar):

numbers.map(=> val + 2);