39

Most programming languages (both dynamically and statically typed languages) have special keyword and/or syntax that looks much different than declaring variables for declaring functions. I see functions as just as declaring another named entity:

For example in Python:

x = 2
y = addOne(x)
def addOne(number): 
  return number + 1

Why not:

x = 2
y = addOne(x)
addOne = (number) => 
  return number + 1

Similarly, in a language like Java:

int x = 2;
int y = addOne(x);

int addOne(int x) {
  return x + 1;
}

Why not:

int x = 2;
int y = addOne(x);
(int => int) addOne = (x) => {
  return x + 1;
}

This syntax seems more natural way of declaring something (be it a function or a variable) and one less keyword like def or function in some languages. And, IMO, it is more consistent (I look in the same place to understand the type of a variable or function) and probably makes the parser/grammar a little bit simpler to write.

I know very few languages uses this idea (CoffeeScript, Haskell) but most common languages have special syntax for functions (Java, C++, Python, JavaScript, C#, PHP, Ruby).

Even in Scala, which supports both ways (and has type inference), it more common to write:

def addOne(x: Int) = x + 1

Rather than:

val addOne = (x: Int) => x + 1

IMO, atleast in Scala, this is probably the most easily understandable version but this idiom is seldom followed:

val x: Int = 1
val y: Int = addOne(x)
val addOne: (Int => Int) = x => x + 1

I am working on my own toy language and I am wondering if there are any pitfalls if I design my language in such a way and if there are any historical or technical reasons this pattern is not widely followed?

closed as primarily opinion-based by gnat, Bart van Ingen Schenau, GrandmasterB, user40980, jwenting Sep 29 '14 at 11:19

Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise. If this question can be reworded to fit the rules in the help center, please edit the question.

  • 9
    The reason is likely historical. Whoever did it first did it that way, and everyone else copied. But I doubt we can know for sure. – user22815 Sep 26 '14 at 18:59
  • 29
    I think it is because a function or method simply is not just another named entity. In functional languages, they are (you can pass them around etc.) but in other languages (like Java) a function or method is something entirely different from a simple variable and cannot be treated as such (I admit Java 8 kind of weakens this statement), so it makes sense to define functions/methods differently, since they behave differently. – 11684 Sep 26 '14 at 19:46
  • 15
    I disagree that your proposal is a more natural way of declaring functions. I really dislike Coffeescript's syntax and part of that is to do with its function declaration syntax. The fact is, if it ain't broke don't fix it. Writing 'def' or 'function' is not a big deal and is way more obvious an a glance compared to some fancy Perl-like symbols which with unthinking or tired eyes could easily be mistaken for something else. On a completely personal note I also think your suggested syntax in the examples look much uglier than the current syntax. – Roy Sep 27 '14 at 14:03
  • 22
    How is '=>' not a "special keyword or syntax for declaring a function"? – TML Sep 28 '14 at 19:25
  • 11
    I don't know about you, but to me (int => int) addOne = (x) => { is a lot more "special" and "complex" than int addOne(int) {... – Bogdan Alexandru Sep 28 '14 at 20:25

12 Answers 12

48

I think the reason is that most popular languages either come from or were influenced by the C family of languages as opposed to functional languages and their root, the lambda calculus.

And in these languages, functions are not just another value:

  • In C++, C# and Java, you can overload functions: you can have two functions with the same name, but different signature.
  • In C, C++, C# and Java, you can have values that represent functions, but function pointers, functors, delegates and functional interfaces all are distinct from functions themselves. Part of the reason is that most of those are not actually just functions, they are a function together with some (mutable) state.
  • Variables are mutable by default (you have to use const, readonly or final to forbid mutation), but functions can't be reassigned.
  • From a more technical perspective, code (which is composed of functions) and data are separate. They typically occupy different parts of memory, and they are accessed differently: code is loaded once and then only executed (but not read or written), whereas data is often constantly allocated and deallocated and is being written and read, but never executed.

    And since C was meant to be "close to the metal", it makes sense to mirror this distinction in the syntax of the language too.

  • The "function is just a value" approach that forms the basis of functional programming has gained traction in the common languages only relatively recently, as evidenced by the late introduction of lambdas in C++, C# and Java (2011, 2007, 2014).

  • 12
    C# had anonymous functions -- which are just lambdas without type inference and a clunky syntax -- since 2005. – Eric Lippert Sep 28 '14 at 1:53
  • 2
    "From a more technical perspective, code (which is composed of functions) and data are separate" - some languages, most well knowingly LISPs don't make that separation and don't really treat code and data too differently. (LISPs are the most well known example, but there are a bunch of other languages like REBOL that do this) – Benjamin Gruenbaum Sep 28 '14 at 8:46
  • 1
    @BenjaminGruenbaum I was talking about compiled code in memory, not about the language level. – svick Sep 28 '14 at 12:05
  • C has function pointers which, at least slightly, can be regarded as just another value. A dangerous value to mess with to be sure, but stranger things have happened. – Patrick Hughes Sep 29 '14 at 1:19
  • @PatrickHughes Yeah, I mention them in my second point. But function pointers are quite different from functions. – svick Sep 29 '14 at 1:21
59

It's because it's important for humans to recognize that functions are not just "another named entity". Sometimes it makes sense to manipulate them as such, but they are still able to be recognized at a glance.

It doesn't really matter what the computer thinks about the syntax, as an incomprehensible blob of characters is fine for a machine to interpret, but that would be nigh-impossible for humans to understand and maintain.

It really is the same reason as why we have while and for loops, switch and if else, etc, even though all of them ultimately boil down to a a compare and jump instruction. The reason is because it's there for the benefit of the humans maintaining and understanding the code.

Having your functions as "another named entity" the way you are proposing will make your code harder to see, and thus harder to understand.

  • 12
    I disagree that treating functions as named entities necessarily makes code harder to understand. That almost certainly true for any code that follows a procedural paradigm, but it may not be true for code that follows a functional paradigm. – Kyle Strand Sep 26 '14 at 21:30
  • 29
    "It really is the same reason as why we have while and for loops, switch and if else, etc, even though all of them ultimately boil down to a a compare and jump instruction." +1 to that. If all programmers were comfortable with machine language, we wouldn't need all these fancy programming languages (high or low level). But the truth is: everybody has a different comfort level as to how close to the machine they want to write their code. Once you find your level, you just have to stick with the syntax given to you (or write your own language specs and compiler if you're really bothered). – Hoki Sep 26 '14 at 21:46
  • 12
    +1. A more concise version might be "Why do all natural languages distinguish nouns from verbs?" – msw Sep 27 '14 at 1:56
  • 2
    "an incomprehensible blob of characters is fine for a machine to interpret, but that would be nigh-impossible for humans to understand and maintain" What a rash qualification for such a modest syntactical change as proposed in the question. One quickly adapts to syntax. This proposal is a much less radical departure from convention than the OO method call syntax object.method(args) was in its time, yet the latter has not proved to be nigh-impossible for humans to understand and maintain. Despite the fact that in most OO languages methods should not be thought of as stored in class instances. – Marc van Leeuwen Sep 27 '14 at 11:51
  • 4
    @MarcvanLeeuwen. Your comment is true and make sense, but some confusion is provoked by the way the original post is redacted. There are actually 2 questions: (i) Why is it this way? and (ii) Why not this way instead?. The answer by whatsisname was more addressing the first point (and alerting on some danger of removing these failsafe), while your comment is more related to the second part of the question. It is possible indeed to change this syntax (and as you described, it has been done many times already...) but it won't suit everybody (as oop doesn't suit everybody either. – Hoki Sep 27 '14 at 15:21
10

You might be interested to learn that, way back in prehistoric times, a language called ALGOL 68 used a syntax close to what you propose. Recognising that function identifiers are bound to values just like other identifiers are, you could in that language declare a function (constant) using the syntax

function-type name = (parameter-list) result-type : body ;

Concretely your example would read

PROC (INT)INT add one = (INT n) INT: n+1;

Recognising the redundancy in that the initial type can be read off from the RHS of the declaration, and being a function type always starts with PROC, this could (and usually would) be contracted to

PROC add one = (INT n) INT: n+1;

but note that the = still comes before the parameter list. Also note that if you wanted a function variable (to which another value of the same function type could later be assigned), the = should be replaced by :=, giving either one of

PROC (INT)INT func var := (INT n) INT: n+1;
PROC func var := (INT n) INT: n+1;

However in this case both forms are in fact abbreviations; since the identifier func var designates a reference to a locally generated function, the fully expanded form would be

REF PROC (INT)INT func var = LOC PROC (INT)INT := (INT n) INT: n+1;

This particular syntactic form is easy to get used to, but it clearly did not have a large following in other programming languages. Even functional programming languages like Haskell prefer the style f n = n+1 with = following the parameter list. I guess the reason is mainly psychological; after all even mathematicians don't often prefer, as I do, f = nn + 1 over f(n) = n + 1.

By the way, the above discussion does highlight one important difference between variables and functions: function definitions usually bind a name to one specific function value, that cannot be later changed, whereas variable definitions usually introduce an identifier with an initial value, but one that can change later. (It is not an absolute rule; function variables and non-function constants do occur in most languages.) Moreover, in compiled languages the value bound in a function definition is usually a compile-time constant, so that calls to the function can be compiled using a fixed address in the code. In C/C++ this is even a requirement; the equivalent of the ALGOL 68

PROC (REAL) REAL f = IF mood=sunny THEN sin ELSE cos FI;

cannot be written in C++ without introducing a function pointer. This kind of specific limitations justify using a different syntax for function definitions. But they depend on the language semantics, and the justification does not apply to all languages.

  • 1
    "Mathematicians don't prefer f = nn + 1 over f (n) = n + 1" ... Not to mention physicists, who like to write only f = n + 1 ... – leftaroundabout Sep 28 '14 at 16:46
  • 1
    @leftaroundabout that's because ⟼ is a pain to write. Honestly. – Pierre Arlaud Sep 29 '14 at 10:11
8

You mentioned Java and Scala as examples. However, you overlooked an important fact: those aren't functions, those are methods. Methods and functions are fundamentally different. Functions are objects, methods belong to objects.

In Scala, which has both functions and methods, there are the following differences between methods and functions:

  • methods can be generic, functions can't
  • methods can have no, one or many parameter lists, functions always have exactly one parameter list
  • methods can have an implicit parameter list, functions can't
  • methods can have optional parameters with default arguments, functions can't
  • methods can have repeated parameters, functions can't
  • methods can have by-name parameters, functions can't
  • methods can be called with named arguments, functions can't
  • functions are objects, methods aren't

So, your proposed replacement simply doesn't work, at least for those cases.

  • 2
    "Functions are objects, methods belong to objects." Is that supposed to apply to all languages (like C++)? – svick Sep 27 '14 at 4:25
  • 9
    "methods can have by-name parameters, functions can't" - this isn't a fundamental difference between methods and functions, it's just a quirk of Scala. Same with most (all?) of the others. – immibis Sep 27 '14 at 5:47
  • 1
    Methods are fundamentally just a special kind of functions. -1. Note that Java (and by extension Scala) doesn't have any other kind of functions. It's "functions" are objects with one method (in general functions may not be objects or even first class values; whether they are depends on the language). – Jan Hudec Sep 28 '14 at 9:15
  • @JanHudec: Semantically, Java has both functions and methods; it uses the terminology "static methods" when referring to functions, but such terminology doesn't erase the semantic distinction. – supercat May 20 '15 at 17:14
3

Turning the question around, if one isn't interested in trying to edit source code on a machine which is extremely RAM-constrained, or minimize the time to read it off a floppy disk, what's wrong with using keywords?

Certainly it's nicer to read x=y+z than store the value of y plus z into x, but that doesn't mean that punctuation characters are inherently "better" than keywords. If variables i, j, and k are Integer, and x is Real, consider the following lines in Pascal:

k := i div j;
x := i/j;

The first line will perform a truncating integer division, while the second will perform a real-number division. The distinction can be made nicely because Pascal uses div as its truncating-integer-division operator, rather than trying to use a punctuation mark which already has another purpose (real-number division).

While there are a few contexts in which it can be helpful to make a function definition concise (e.g. a lambda which is used as part of another expression), functions are generally supposed to stand out and be easily visually recognizable as functions. While it might be possible to make the distinction much more subtle and use nothing but punctuation characters, what would be the point? Saying Function Foo(A,B: Integer; C: Real): String makes it clear what the function's name is, what parameters it expects, and what it returns. Maybe one could shorten it by six or seven characters by replacing Function with some punctuation characters, but what would be gained?

Another thing to note is that there is an most frameworks a fundamental difference between a declaration which will always associate a name with either a particular method or a particular virtual binding, and one which creates a variable which initially identifies a particular method or binding, but could be changed at runtime to identify another. Since these are very semantically very different concepts in most procedural frameworks, it makes sense that they should have different syntax.

  • 3
    I don't think this question is about conciseness. Especially since for example void f() {} is actually shorter than the lambda equivalent in C++ (auto f = [](){};), C# (Action f = () => {};) and Java (Runnable f = () -> {};). The conciseness of lambdas comes from type inference and omitting return, but I don't think that's related to what this questions asks. – svick Sep 26 '14 at 23:16
3

The reasons that I can think of are:

  • It is easier for the compiler to know what we declaring.
  • It's important for us to know (in a trivial way) whether this is a function or a variable. Functions are usually black boxes and we don't care about their internal implementation. I dislike type inference on return types in Scala, because I believe that it's easier to use a function that has a return type: it is often the only documentation provided.
  • And the most important one is the following the crowd strategy that is used in designing programming languages. C++ was created to steal C programmers, and Java was designed in a way that doesn't scare C++ programmers, and C# to attract Java programmers. Even C#, which I think is a very modern language with an amazing team behind it, copied some mistakes from Java or even from C.
  • 2
    "…and C# to attract Java programmers" — this is questionable, C# is much more similar to C++ than is Java. and sometimes it looks like it was made intentionally incompatible with Java (no wildcards, no inner classes, no return type covariance…) – Sarge Borsch Sep 27 '14 at 18:16
  • 1
    @SargeBorsch I don't think it was intentionally made incompatible with Java, I'm pretty sure the C# team simply tried to do it right, or do it better, however you want to look at it. Microsoft already wrote their own Java & JVM and got sued. So C# team was likely very motivated to eclipse Java. It is surely incompatible, and better for it. Java started out with some basic limitations and I'm glad that C# team chose, for example, to do generics differently (visible in the type system and not just sugar). – codenheim Sep 27 '14 at 19:05
2

Well, the reason might be, that those languages are not functional enough, so to say. In other words, you rather seldomly define functions. Thus, the use of an extra key word is acceptable.

In languages of the ML or Miranda heritage, OTOH, you define functions most of the time. Look at some Haskell code, for instance. It's literally mostly a sequence of function definitions, many of those have local functions and local functions of those local functions. Hence, a fun keyword in Haskell would be a mistake as great as requiring an assingment statement in an imperative language to start with assign. Cause assignment is probably by far the single most frequent statement.

1

This might be useful on dynamic languages where the type is not that important, but it's not that readable in static typed languages where always you want to know the type of your variable. Also, in object-oriented languages it's pretty important to know the type of your variable, in order to know what operations it supports.

In your case, a function with 4 variables would be:

(int, long, double, String => int) addOne = (x, y, z, s) => {
  return x + 1;
}

When I look at the function header and see (x, y, z, s) but I do not know the types of these variables. If I want to know the type of z which is the third parameter, I'll have to look at the beginning of the function and start counting 1, 2, 3 and then to see that the type is double. In the former way I look directly and see double z.

  • 3
    Of course there's that wonderful thing called type inference, which allows you to write something like var addOne = (int x, long y, double z, String s) => { x + 1 } in a non-moronic statically typed language of your choice (examples: C#, C++, Scala). Even the otherwise very limited local type inference used by C# is completely sufficient for this. So this answer is merely criticizing a specific syntax that's dubious in the first place, and not actually used anywhere (although Haskell's syntax has a very similar problem). – amon Sep 26 '14 at 19:46
  • 4
    @amon His answer may be criticizing the syntax, but it also points out that the purposed syntax in the question may not be "natural" to everyone. – jmstoker Sep 26 '14 at 23:26
  • 1
    @amon The usefulness of type inference is a not a wonderful thing for everyone. If you read code more often that you write code, then type inference is bad because you have to infer in your head the type when reading the code; and is way more faster to read the type than to actually think about what type is used. – m3th0dman Sep 28 '14 at 13:18
  • 1
    The criticism seems valid to me. For Java, the OP seems to think [input, output, name, input] is a simpler / clearer function def than simply [output, name, input]? As @m3th0dman states, with longer signatures, the OP's 'cleaner' approach becomes very long-winded. – Michael Sep 29 '14 at 11:01
1

Personally, I see no fatal flaw in your idea; you may find that it's trickier than you expected to express certain things using your new syntax, and/or you may find that you need to revise it (adding various special cases and other features, etc), but I doubt you'll find yourself needing to abandon the idea entirely.

The syntax you've proposed looks more or less like a variant of some of the notation styles sometimes used to express functions or types of functions in mathematics. This means that, like all grammars, it will probably appeal more to some programmers than others. (As a mathematician, I happen to like it.)

However, you should note that in most languages, the def-style syntax (i.e. the traditional syntax) does behave differently from a standard variable assignment.

  • In the C and C++ family, functions aren't generally treated as "objects", i.e. chunks of typed data to be copied and put on the stack and whatnot. (Yes, you can have function pointers, but those still point at executable code, not at "data" in the typical sense.)
  • In most OO languages, there's special handling for methods (i.e. member functions); that is, they're not just functions declared inside the scope of a class definition. The most important difference is that the object on which the method is being called is typically passed as an implicit first parameter to the method. Python makes this explicit with self (which, by the way, is not actually a keyword; you could make any valid identifier the first argument of a method).

You need to consider whether your new syntax accurately (and hopefully intuitively) represents what the compiler or interpreter is actually doing. It may help to read up on, say, the difference between lambdas and methods in Ruby; this will give you an idea of how your functions-are-just-data paradigm differs from the typical OO/procedural paradigm.

1

For some languages functions are not values. In such a language to say that

val f = << i : int  ... >> ;

is a function definition, whereas

val a = 1 ;

declares a constant, is confusing because you are using one syntax to mean two things.

Other languages, such as ML, Haskell, and Scheme treat functions as 1st class values, but provide the user with a special syntax for declaring function valued constants.* They are applying the rule that "usage shortens form". I.e. if a construct is both common and verbose, you should give the user a shorthand. It is inelegant to give the user two different syntaxes that mean exactly the same thing; sometimes elegance should be sacrificed to utility.

If, in your language, functions are 1st class, then why not try to find a syntax that is concise enough that you won't be tempted to find a syntactic sugar?

-- Edit --

Another issue no one has brought up (yet) is recursion. If you allow

{ 
    val f = << i : int ... g(i-1) ... >> ;
    val g = << j : int ... f(i-1) ... >> ;
    f(x)
}

and you allow

{
    val a = 42 ;
    val b = a + 1 ;
    a
} ,

does it follow that you should allow

{
    val a = b + 1 ; 
    val b = a - 1 ;
    a
} ?

In a lazy language (like Haskell), there is no issue here. In an language with essentially no static checks (like LISP), there is no issue here. But in a statically-checked eager language, you have to be careful about how the rules of static checking are defined, if you want to allow the first two and forbid the last.

-- End of Edit --

*It might be argued that Haskell does not belong in this list. It provides two ways to declare a function, but both are, in a sense, generalizations of the syntax for declaring constants of other types

0

There is a very simple reason to have such a distinction in most languages: there is a need to distinguish evaluation and declaration. Your example is good: why not like variables? Well, variables expressions are immediately evaluated.

Haskell has a special model where there is no distinction between evaluation and declaration, which is why there is no need for a special keyword.

  • 2
    But some syntax for lambda functions also solves this, so why not use that? – svick Sep 26 '14 at 21:06
0

Functions are declared differently from literals, objects, etc. in most languages because they are used differently, debugged differently, and pose different potential sources of error.

If a dynamic object reference or a mutable object is passed to a function, the function can change the value of the object as it runs. This kind of side effect can make it difficult to follow what a function will do if it is nested within a complex expression, and this is a common problem in languages like C++ and Java.

Consider debugging some sort of kernel module in Java, where every object has a toString() operation. While it may be expected that the toString() method should restore the object, it may need to disassemble and reassemble the object in order to translate its value to a String object. If you're trying to debug the methods that toString() will be calling (in a hook-and-template scenario) to do its work, and accidentally highlight the object in the variables window of most IDE's, it can crash the debugger. This is because the IDE will try to toString() the object which calls the very code you're in the process of debugging. No primitive value ever does crap like this because the semantic meaning of primitive values is defined by the language, not the programmer.

Not the answer you're looking for? Browse other questions tagged or ask your own question.