11
$\begingroup$

What are some ways that existing programming languages handle function definitions, and what are the pros and cons of each method? By "function definitions" I mean either named (i.e. function foo(a, b) { return a + b; } in JavaScript) or anonymous (lambda) functions (i.e. (a, b) => a + b in JS). I'm not looking for special kinds of functions (e.g. asynchronous, generator, etc. where definition syntax might be different) but general ones.

I'm looking for answers in terms of readability, familiarity, verbosity, and any other advantages/disadvantages that may come with different syntaxes.

$\endgroup$

5 Answers 5

8
$\begingroup$

Functions should be a special case of lambdas

For example:

// lambda
(args) => code

// function

name = (args) => code

In this way, named functions become variables that have been assigned a function object.

Pros

  • Only one syntax structure is needed for lambdas and functions, meaning the language is easier to learn and more orthogonal.
  • In a language that treats functions as first-class citizens, this drives home the idea that functions are objects that can be passed around just like any other value.
  • The named function syntax becomes more analogous to mathematics, where you would write f(x) = some expression

Cons

  • In a language where functions aren't first-class citizens, this doesn't make as much sense, and might not even be supported if you don't have lambdas.
  • It can be less clear what is a procedure and what isn't.
  • Multiline function bodies would need to be expressions to fit with the lambda model, meaning you would need to rework your language design to fit around that.
  • Overloads are made impossible.
$\endgroup$
7
  • $\begingroup$ This is how CoffeeScript does it -- all functions are arrow functions -- which threw me off at first, since I'd never seen it before. $\endgroup$ Commented May 17, 2023 at 1:31
  • $\begingroup$ Lua does that too $\endgroup$ Commented May 17, 2023 at 1:51
  • $\begingroup$ Overloads could be supported using a magic stdlib function or a bit of syntax, say, name = overload((a: int) => a + 1, (a: string) => a[0]) $\endgroup$ Commented May 17, 2023 at 4:39
  • 2
    $\begingroup$ @Nuoji that's why it's an answer that gives the pros and cons of the approach - it's an opinion with both sides considered $\endgroup$ Commented May 17, 2023 at 7:47
  • 1
    $\begingroup$ Well, perhaps overloading can be supported simply replacing name = ... by name += .... [Yeah, it entangles name lookup with order of evaluation... :-( ] $\endgroup$ Commented Aug 11, 2023 at 16:15
4
$\begingroup$

The possible choices for function syntax depends very much on the variable and constant declaration syntax.

If anonymous functions / lambdas are needed, they need to be constructed exactly in a way that grammar ambiguities are avoided, and obviously this depends on the overall grammar.

In the lambda case, many languages also offer shorter forms with types inferred. As lambdas as often used as callbacks or computation functions, this often increases readability for short functions.

An overview of different syntax I often use is this.

There are no clear cut rules, while one would prefer function and lambda follow each other closely, there can be trade-offs with other concerns such as the overall grammar complexity, readability etc.

Some examples:

C++ (note how the capture list with [] also serves to disambiguate the grammar somewhat)

int foo(int a) { ... }
x = [](int a) -> int { ... };

Ruby (syntax is different from function construction, but consistent with other constructs using blocks)

def foo(a) ... end
x = lambda { |a| ... }

Go (function and lambda declarations are very similar)

func foo(a int) int { ... }
x = func(a int) int { ... }

Java (note the special short syntax available for lambdas)

int foo(int a) { ... }
x = a -> { ... }

Swift (similar to Java it has special inferred forms available for lambdas)

func foo(a: Int32) -> Int32 { ... }
y = { a in ... }
$\endgroup$
1
  • $\begingroup$ Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center. $\endgroup$ Commented May 17, 2023 at 8:08
3
$\begingroup$

I think overloading would be really difficult when assigning a lambda. I can't even conceive of how this would be done:

foo = (x) => x + 1
foo = (x, y) => x + y

Whereas syntax for explicitly declaring a function makes overloading a breeze:

func foo(x): return x + 1
func foo(x, y): return x + y

The first case makes me think it would be pretty difficult for the compiler to distinguish that the second assignment is an overload as opposed to a mutation or redeclaration of the foo variable.

$\endgroup$
3
  • 1
    $\begingroup$ Thats why you dont overload with these kinds of functions $\endgroup$ Commented May 17, 2023 at 1:52
  • 2
    $\begingroup$ You could do something like let f = { (x) => x + 1, (x,y) => x + y} like you would do in math $\endgroup$ Commented May 17, 2023 at 4:48
  • 1
    $\begingroup$ Well, perhaps overloading can be supported simply replacing name = ... by name += .... [Yeah, it entangles name lookup with order of evaluation... :-( ] [Comment repeated from another answer.] $\endgroup$ Commented Aug 11, 2023 at 16:20
3
$\begingroup$

ML Style:

let add x y = x + y

let apply_as_tuple f a b = f (a, b)

Pros:

  • Looks the same as the value syntax, enforcing functions-are-values
  • Works with automatic currying
  • Fairly simple

Cons:

  • Requires a keyword (let)
  • Doesn't look as good with non-curried arguments
  • Enforces whitespace function call syntax, which may not be ideal
$\endgroup$
1
$\begingroup$

Lambda functions with simple arrow (=> or ->) syntax is necessary for implementing of chained streaming API:

map filter takeWhile dropWhile reduce, etc.

See how ugly it is in Go:

even := lo.Filter([]int{1, 2, 3, 4}, func(x int, index int) bool {
    return x % 2 == 0
})
// []int{2, 4}

And in Java:

int[] a = {1, 2, 3, 4};
int[] even = Arrays.stream(a)
    .filter(i -> i % 2 == 0)
    .toArray();
$\endgroup$
3
  • $\begingroup$ Swift has this without arrow functions per se; that filter call would be .filter { $0.isMultiple(of: 2) }. $\endgroup$ Commented May 17, 2023 at 10:51
  • $\begingroup$ Yes, and Kotlin can also do that by a scope variable called it. I just simply hate the Go-style lambdas. $\endgroup$ Commented May 18, 2023 at 14:10
  • $\begingroup$ Even when you write it out in full, Swift uses in rather than Kotlin’s arrow. $\endgroup$ Commented May 18, 2023 at 14:34

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.