Syntax comparison for function call

I have the following ideas for function call syntax
(the function sum() gets two integers as named parameters a and b and returns int):

a) c = sum.a( 42 ).b( 42 )

b) c = sum( a:42 b:42 )

Which one would you prefer and why?
In the given language there are only named parameters, all of which are required
in the call in the order they are defined.

Comment viewing options

Does your language support

Does your language support currying? If so I prefer (a), since it looks like I can do the application in stages:

temp = sum.a(42)
c = temp.b(42)

If your language does not support currying, I prefer (b), since (a) would look misleading. The reason I wouldn't prefer (b) if currying is available is that it has redundant syntax:

temp = sum(a:42)
c1 = temp(b:42)
c2 = sum(a:42 b:42)
c3 = sum(a:42)(b:42)

The (a) syntax looks like method calls on sum. My intuition would tell me that "sum.a(42)" would set the value of "a", perhaps in a returned copy of "sum" that I can chain further method calls on to. This intuition looks right in this case.

The (b) syntax looks a bit like Smalltalk written in a more-C-like way, although Smalltalk would probably make "sum" a message for "a":

42 sumB: 42

Currying is not supported in

Currying is not supported in this language.

I also see how syntax a) can be misunderstood as beeing a record/structure/object access. But the language does not have member access like this.

Why require arguments to be passed in the correct order?

For me the value of named parameters is that they let me forget about the order of arguments and enable me to just pass them by name (and the names of arguments are easier to remember than the order).

That is one use of named

That is one use of named parameters, especially as they appear in Python for example.

Other approaches, for example in Smalltalk, Agda and Maude, treat the parameter names as part of the function/method name. In these situations it makes no sense to re-order the parameters (and it's difficult to imagine using them with currying).

For example, here is the statement "if x then y else z" in these languages:

Smalltalk:
x ifTrue: y ifFalse: z.

Agda:
if x then y else z

Maude
if x then y else z fi

Note that Agda and Maude use 'mixfix' names, which makes the declarations of these functions/methods look very different.

In Smalltalk:

In the True class:

ifTrue: trueBlock ifFalse: falseBlock
"We are True, so return the first arg."
^ trueBlock

In the False class:

ifTrue: trueBlock ifFalse: falseBlock
"We are False, so return the second arg."
^ falseBlock

In Agda:

if_then_else_ : {A : Set} â†’ Bool â†’ A â†’ A â†’ A
if true  then x else y = x
if false then x else y = y

In Maude (for some type A):

op true : Bool .
op false : Bool .
op if_then_else_fi : Bool A A -> A .
vars X Y : A .
eq if true  then X else Y = X .
eq if false then X else Y = Y .

However, we can recover Smalltalk-like keywords by naming our functions appropriately:

In Agda:

-- Definition
ifCondition:_then:_else:_ : {A : Set} â†’ Bool â†’ A â†’ A â†’ A
ifCondition: true  then: x else: y = x
ifCondition: false then: x else: y = y

-- Usage
myVar = ifCondition: myBool then: myThen else: myElse

In Maude:

--- Definition
op true : Bool .
op false : Bool .
op ifCondition:_then:_else:_ : Bool A A -> A .
vars X Y : A .
eq ifCondition: true  then: X else: Y = X .
eq ifCondition: false then: X else: Y = Y .

--- Usage
ifCondition: myBool then: myThen else: myElse .

That is one use of named

Thanks for the response. I already knew that about Smalltalk but since I've never actually used Smalltalk it slipped my mind...

blocks as delayed evaluation

Your syntax comparison skips over something interesting in terms of evaluation order semantics in Smalltalk if-statement-messages, which I don't recall being spelled out clearly in classic old school Smalltalk docs. Code shown to handle keyword message ifTrue:ifFalse: in the True or False classes is not normally run, because a compiler will inline the handling when both args are blocks.

x ifTrue: y ifFalse: z.

Given that syntax, normally Smalltalk will evaluate y and z, both, then dispatch a message to x. But the point of if-statement control flow primitives is conditional evaluation: only one or the other should evaluate, not both. So almost always, both y and z should be block literals, encosed in square brackets, to get delayed evaluation.

In particular, a compiler ought to go, "Is this keyword message one of the ones I inline? And are both arguments blocks? Oh, then I'll just embed those blocks inline and do this directly." So it's actually sugar. It's written like a message, but it's a special form, and doesn't dispatch like a message. And that's why the implementations in True and False don't get called — unless either y or z is not a block. A compiler might even consider a non-block in those positions to be an error you likely didn't intend.

I thought someone might find this weirdly interesting if they had not written a Smalltalk compiler before. Everything looks like a message, but can be turned into something else.

c = sum.a( 42 ).b( 42

c = sum.a( 42 ).b( 42 )

How do you distinguish between passing two arguments to sum versus passing a( 42 ) to sum and passing b( 42 ) to the result of calling sum?

Grace has an interesting solution for this. You only have a . before the first parameter name. So the above would be:

c = sum.a( 42 ) b( 42 )

That makes it clear that b is a parameter for sum. In the case where the arguments are literals (including blocks), you can omit the parentheses:

c = sum.a 42 b 42

As to your other example:

c = sum( a:42 b:42 )

I have a language that used to work that way. (You were passing a single record with two named fields to sum.) I found users didn't like the lack of separators, so the current syntax is now:

c = sum(a: 42, b: 42)

...

This means "pass parameter

This means "pass parameter named 'a' with value 42 and parameter named 'b' with value 42 to the function 'sum', store result in c":

c = sum.a( 42 ).b( 42 )

Whereas this is an application of the result of 'sum' to another function 'inc':

c = inc.x( sum.a( 42 ).b( get_twenty_four() ) )

It is always clear what passes into what function and how function results are passed as parameters.

Your example applies inc to

Your example applies inc to the result of sum, not the other way around.

Since sum doesn't return a function, it might help to use a different example:

incrementAll = map.f( plusOne )
newList = incrementAll.list( oldList )

Can we combine that into one expression? This won't work, because you're saying it's only one function call rather than two:

newList = map.f( plusOne ).list( oldList )