On mobile horizontal scrolling through the code snippets will trigger switching to the next snippet on my phone.
On mobile horizontal scrolling through the code snippets will trigger switching to the next snippet on my phone.
If your standard function call convention is just `f x`, but you also support precedence operators, then `f (x)` automatically becomes possible.
It reminds me of an old Lisp joke, though:
f x -- too mathematical!
(f x) -- too many parenthesis!
f(x) -- just right!:) yet we are very happy to use the same syntax in shell scripts or the shell itself.
In shell, f x y z, x y z are all augments to f. Doesn't matter if f takes one argument, all are passed to the function.
With many functional languages this gets very confusing. IE what ie what does `f f x` do? In shell I know for sure.
In the example f f x, it might be easy to parse. But in f x y z, any of x y z might be functions.
OCaml parses a b c as ((a b) c). In case the compiler can determine that a is a function taking 2 arguments, it will optimise the code, so that it’s effectively a(b, c). But in general, that’s not possible, especially in the case where the compiler determines that a is a function with a single argument (in which case, it’s return value must be another function, which is in turn called with c) or when a is a first-class function (e.g. passed as an argument)
In Ruby however it is a bit more ugly
def f x
x + 1
end
puts f f 1
> 3xargs -0 -n1 bash -c 'mv $1 ${1//.js/.ts}' --
Everything to the right of xargs is a string argument passed to xargs.
f $(g x)
To pass the output of g x to f. grep "hello ($(cat patterns.txt| tr '\n' '|'|grep ')$^)" <(ssh other.host "cat ~/file.txt") |tee >(grep 'a' >As.txt) >(grep 'b' > Bs.txt)
[yes, gratuitous use of cat, sue me]and the nice thing about (f x) is that the parenthesis group f with x; so you have the whole call inside the (). Consistent and simple to understand.
vs i.e. print(f"a string"), where it isn't even clear that the "f" is a function call.
Hence the joke. It's one of those jokes that earns a loud sigh from me, rather than a chuckle.
The drawback is that they are put on the same level, whereas in most people’s minds the function is a fundamentally different thing from the argument(s). The “f(x)” syntax reflects that asymmetry.
Of course, theoretically you could also view the function as a parameter of the computation and/or the arguments as specifying an operation (in particular if those are also functions), but for most concrete function invocation that's not generally the mental model. E.g. in "sin(x)" one usually has in mind to compute the sine, and x is the input for that computation. One doesn't think "I want to do x, and `sin` is the input of that operation I want to do". One also doesn't think "I want to do computation, and `sin` and `x` are inputs for that computation". It's why you may have mentally a sine graph ranging over different x values, but you don't imagine an x graph ranging over different functions you could apply to x.
Incidentally, that’s also why we use syntax highlighting. One could, of course, use syntax highlighting instead of symbols to indicate the difference between function and arguments (between operation and operands), but that would interfere with the use of syntax highlighting for token categories (e.g. for literals of different types).
That's the joke
A lisp paren can do both jobs: expression and scopes.
Using different symbols for different purposes makes sense, it helps humans to parse correctly faster.
Mainly, you don't look at the parenthesis when reading; you look at that let and your eyes rely on indentation for structure.
Also, the ending paren doesn't have anything to help you see what it is on its own.
)))) is like a ground symbol in a schematic:
(+5V
(+10V
(-10V
(INPUT3 ...))))
----- local "ground" for all the above
(different circuit)As a side note, I believe that different people fundamentally have different programming languages that objectively suit them best, due to differences in their respective psychology and way of thinking and perceiving. It’s interesting to discuss trade-offs, but in the end there is no single truth about which language is better overall — it depends on the task and on the person. There’s no “one size fits all”. What’s valuable is to understand why a certain syntax or language might work better for certain people.
( can mean: function call, part of an expression to be evaluated first, regex capture group
{ can mean: scope [of ... loop, if, lambda, function etc.], object definition, string interpolation code i.e. ${..}, class definition
There are probably other things I am not thinking of.
The one that trips me in JS is code like x.map(v => {...v, v2}) breaks because the compiler sees the { as the beginning of a scope, not the object definition I intended.
The working solution is x.map(v => ({...v, v2}))
But x.map(v => v+1) is allowed.
I don't think the compiler could figure out what you meant because an object definition might be a lambda function body. For example { myvar } can be both an object definition, and a function that returns myvar.
Java, one pair of parens:
int x = 1;
int y = x + 1;
System.out.println(y);
Clojure, six pairs of brackets: (let [x 1 y (+ x 1)] ((. (. System out) println) y))The negative reactions that a lot of people have toward Lisp's parentheses are not because of the call function syntax but because parentheses are used everywhere else in Lisp's syntax.
x fFor the same reason you'd make 1 + 2 the same as 1 + (2). Parentheses can be used to group arbitrary subexpressions.
You can design a language which uses `e1 e2` to represent any binary operation you like, but I'd argue that function application is more common than string concatenation, so it's more deserving of that syntax. Plus, it plays nicely with currying.
f g = composition of f then g
x.f = f applied to x
x.f g h = in regular notation h(g(f(x))
Though slightly different precedence rules may be preferable.I think the k in awk considered juxtaposition-as-string-concatenation to have been a mistake by the way.
Some other reasonable choices may be:
- disallowed syntax
- multiplication (which, for matrices, is a special case of function composition and application)
- inner join which can be seen a bit like function composition but for relations instead of functions
- sequencing (ie instead of ‘;’)
If functions are curried, then I suppose the syntax for `f x y` would be `y.(x.f)`, which maybe you could write as `y.x.f` if the associativity worked as such. But that means you have to provide your arguments in reverse order?
If functions are not curried, do you write `(x, y).f`?
A good number of "functional programmers" only have experience with JavaScript these days.
1 and (1) are isomorphic. A single term tuple can be converted to the single term, and vice versa. Having an implicit conversion doesn't seem too crazy.
The biggest issue I suspect would be confusion about the most idiomatic way, or a mix of styles in real-world code bases, that causes confusion or inconsistencies (increases cognitive load for the reader).
Where brackets are used in some languages to construct tuples, you generally need special syntax to represent the case for 1-tuples, like Python where "(1)" is equivalent to "1" but "(1,)" is a 1-tuple containing a single 1 value.
Also in most FP semantics, x and the 1-tuple containing x are not equivalent so the mathematical isomorphism doesn't hold. The tuple itself could be undefined (bottom/unterminating or null, if the language has such a concept), or could contain an undefined value or could be completely well-defined. These three cases are not represented in the semantics of the unbundled value.
(1) - the number 1
1 - the number 1
(1,) - a tuple with one item, which is the number 1
1, - a tuple with one item, which is the number 1