Whitespace syntax for parameter lists

Hey there,

Since the introduction of the “fewer braces” syntax (which I’m a fan of), there is an odd inconsistency in the language: unary functions can be called with whitespace syntax, but parens and commas are required to call a function of more than one parameter:

// unary function call
foo:
  bar.can(be) ++ "a complex".expression

// binary function call
foo(
  bar.can(be) ++ "a complex".expression,
  baz
)

And the longer I’ve been using fewerBraces, the more I feel that every such function call sticks out visually like a sore thumb, especially given that it’s actually possible to mix paren/comma parameter lists with fewerBraces function calls:

foo(
  bar:
    baz ++ qux,
  bla
)
// equivalent to foo(bar(baz ++ qux), bla)

If you find this beautiful, please don’t leave a comment :stuck_out_tongue_winking_eye:
This is not a contrived case: such code often occurs for example in zio-test where a suite could be defined like so:

def spec =
  suite("MySuite")(
    test("first test"):
      doTheThing(),
    test("second test"):
      doTheOtherThing()
  )

It shouldn’t be too hard to fix this: we can already infer semicolons, so why not add a syntax that works the same way but infers commas rather than semicolons?
I’m going to use @ as a mock syntax, but of course that’s completely open to debate.

E. g.

foo @
  bar
  baz
// equivalent to foo(bar, baz)

var assignments in Scala are expressions that evaluate to the Unit value, so the following could be read as a call to function foo where the expressions passed to it are var assignment expressions.

foo @
  a = b
  x = y
// could be read as foo((a = b), (x = y))

However both var assignments and passing the Unit value to a function are relatively rare in idiomatic Scala code, so this interpretation isn’t particularly useful. Rather, this syntax should be read as a function call with named parameters: foo(a = b, x = y).

Do you feel the same way about multi-line parameter lists as I do? Is @ a good syntax or should we go for something else?

Personally not a fan.

  • Just as there can be too much syntax, there can also be too little. It’s a judgement call exactly where the line lies, but

    • My only experience with a language that had optional parentheses and optional commas in multi-argument function calls (Coffeescript) felt definitely on the too-little side of things.
    • Python, which has indentation-blocks but requires parentheses for functions, seems about a good balance.
    • Even Ruby, which has optional parens but mandatory commas, is pushing the boundary IMO
  • In most languages, there is the concept of a “block” that has special handling and is distinct from other function parameters. Ruby blocks have a different type from normal parameters. Swift and Kotlin both treat blocks specially as the “last parameter of the function”. XML blocks can contain child elements where attributes can’t. Even Scala has always treated the “single parameter in parameter list” scenario specially, allowing it to be called with curlies instead of parens. So there is a ton of precedence for treating single-parameter blocks specially across the industry, and Scala allowing indentation syntax for single-parameter blocks but not for multi-parameter blocks fits right in

2 Likes

I like the idea because it obviates trailing comma, which I also like.

The Achilles heel of trailing comma is that people forget the comma and neglect to place the closing paren on the next line. That is, it’s a style choice.

Although I don’t like a method signature that takes multiple thunks in one parameter list, I would be more open to applications that supply a complex expression as an arg if args were neatly separated, each on a line.

This syntax would also relegate all arguments over how many arguments belong on a line to a distant and ugly past.

It may also resolve the debate over “how many parameters are too many?”

If the formatting rule is that applications of (for example) more than five explicit args must use named args, then an application of many args is no stranger or more inscrutable than a series of assignments.

I’ll resurrect my suggestion to use double colon, not to sink the proposal but because maybe it works after all (the mnemonic is that it suggests a list of args):

def f(a: Int, b: => Unit, c: Boolean): Unit
f::
  a = 42
  b =
    x = 27
  if v > limit then y else n
end f // also repurpose end to mean end of invocation

Today everyone constructs lists using “leading infix” notation:

val ss =
     "one"
  :: "two"
  :: "three"
  :: Nil

so borrowing :: at EOL for this purpose is a small price.

Since leading infix requires a space after the operator, “trailing inferred paren” can require no space, so that list constructors who are behind the times can continue to use trailing cons forever.

I’ve suggested end for the end of an invocation on a discussion or ticket. Perhaps parenless, commaless invocation is the moment to revive it.

Please don’t make my cry because America is Kamaless.

1 Like

Related:

https://www.reddit.com/r/scala/comments/1fey817/optional_parantheses_akin_to_optional_braces_in/

I guess @LPTK will enjoy seeing his @ proposal recycled also here. :slightly_smiling_face:

Of course I’m in the fraction of the useless comma and

        )
      )
    )
  )
)

hatters (as this is just the same nonsense as with curly braces) but I’m not sure about the @ syntax. The Reddit thread discusses some consequences, and I think there is something to the arguments against this.

The only solution to not introduce another method call syntax like in

intStream .map @ _ + 1 .fold @ 0 @ sum

would be to allow it only for multi line calls.

But this leads also to chaos…