10
$\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

7
$\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$
    – Bbrk24
    Commented May 17, 2023 at 1:31
  • $\begingroup$ Lua does that too $\endgroup$
    – Seggan
    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$
    – RubenVerg
    Commented May 17, 2023 at 4:39
  • $\begingroup$ I find this a too opinionated answer. There is no strict need for them to be the same. It is desirable yes, but there are other considerations that are more important. $\endgroup$
    – Nuoji
    Commented May 17, 2023 at 7:41
  • 1
    $\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$
    – lyxal
    Commented May 17, 2023 at 7:47
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$
    – Seggan
    Commented May 17, 2023 at 1:52
  • 1
    $\begingroup$ You could do something like let f = { (x) => x + 1, (x,y) => x + y} like you would do in math $\endgroup$
    – mousetail
    Commented May 17, 2023 at 4:48
  • $\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$
    – Pablo H
    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$
    – Bbrk24
    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$
    – wolray
    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$
    – Bbrk24
    Commented May 18, 2023 at 14:34

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .