I've never worked in a language without parents for function calls so I share their confusion reading Haskell or Ruby code at a glance.
I don't think a language needs to bend for non-users but being intelligible without knowing the language canI be nice.
As an outsider, the examples:
A(B(C)) A(B,C) A(B)(C)
Could all be valid Python but still communicate a little bit about A B and C. Without parents they'd all look like "A B C". I'm sure it's more obvious if you know those languages.
A(B(C)) would be `a (b c)` or `a $ b c` or `(a . b) c`. The first is the most vanilla way, the second is convenience (basically "evaluate everything after $ first, then plug it in") and the third uses the function composition operator `.`.
We can define `$` ("apply a function to an argument") as:
f $ x = f x
We can define `.` ("compose two functions") as (where `\x -> ...` is an anonymous function): f . g = \x -> f (g x)
Equivalents in, say, Javascript would be: function dollar(f, x) { return f(x); }
function dot(f, g) { return function(x) { return f(g(x)); }
People mostly use `$` because of its precedence rules, which cause everything to its left to be treated as its first argument, and everything to the right as its second, i.e. we can use it to remove grouping parentheses like: (any complicated thing) (another complicated thing)
with: any complicated thing $ another complicated thing
It's also useful for partially applying, e.g. `map ($ x) fs` will apply each function in the list `fs` to the argument `x`.No, that would just be a(b,c). You would use parentheses to express the others. a(b(c)) would be a (b c). a(b)(c) would be (a b) c. IMO it's still perfectly readable, you just have to know it and get used to it (which, granted, can be difficult if you're only used to paren-ized languages, but not an impossible ask, certainly not, imo, a deal-breaker).
The latter would be written `a([b, c])` in other languages. These are all isomorphic (we're calling `a` and giving access to `b` and `c`), but can confuse people who are new to the syntax.
I mean, why not give invocation its own one character operator, it's used all over the place.
the parent has a point you have no point.
foo
is this a function call or a function value? foo()
now it's explicit.Your sarcasm just does not work.
> calling functions with args is harder than it should be
care to explain? because it is not harder.
Whether that is a partially or fully applied function doesn’t matter in the first syntax, but in the second syntax you have to add ugly parens to disambiguate the partially applied function from the function call.
As soon as you get serious about type-level functions, the first syntax beats the shit outta the second.
foo bar
is it calling bar then foo or passing bar as a value for foo?> As soon as you get serious about type-level functions, the first syntax beats the shit outta the second.
Obviously it is not. Since you still need parenthesis for disambiguation and edge cases.
foo(bar)
There is no ambiguity here. Simple rules are the best ones: parentheses for all function call.Interestingly a function which takes a unit value as argument (AKA a "thunk") is isomorphic to its return value, i.e. wrapping the body in a function doesn't change its meaning. Yet it can be useful to prevent side-effects from triggering, even if the effect is as benign as "heat up the CPU by performing lots of calculations".
Now I will take sides and point out that if we have currying, all of the following are the same:
f(a, b, c, d)
f(a, b, c)(d)
f(a, b)(c, d)
f(a, b)(c)(d)
f(a)(b, c, d)
f(a)(b)(c, d)
f(a)(b)(c)(d)
f(a)(b, c)(d)
Not only does this proliferation of forms over-complicate a language, but it's also completely redundant as a no-parens language would write it as `f a b c d`.and I don't see how it makes anything more readable. It doesn't.
I was giving an objective, measurable example where the `f(a, ...)` syntax is "harder": namely that it causes a proliferation of (curried) function application forms, all of which are equivalent, compared to just one in the `f a ...` form. As it stands, such parenthesised function calls don't even have a normal form! We could add one, by adding a new rule to the language, but even then there's ambiguity about what that rule might be: should we rewrite expressions of the form `f(a)(b)` into `f(a, b)`, thus making `f(a, b, c, d)` the normal form; or should we rewrite expressions of the form `f(a, b)` into `f(a)(b)`, thus making `f(a)(b)(c)(d)` the normal form?
Issues like this would plague anyone trying to manipulate programs programatically, e.g. writing interpreters, compilers, formatters, linters, documentation generators, HTML/LaTeX renderers, static analysers, (structured) macro expanders, minifiers, obfuscaters, deobfuscaters, (structured) diff/patch/VCS/etc., auto-completers, template/skeleton code generators, etc.
Can you expand on that? How does no parens make it harder to pass around first class functions? In Gluon (and in Haskell, Purecsript, Idris, etc), you just pass around the function name. If you have "inc x = 1 + x" and you want to pass that to map, you just pass it by name: "map inc [1, 2, 3]"
What could be simpler?
In languages where there can be functions with no arguments, if referring to the function without parentheses calls it, it can be inconvenient to get a reference to the function itself.
From your other post: "In both Gluon and Haskell, functions without arguments can be represented as functions over the unit type: f: () -> SomeType"
Isn't `f` a constant here too?, how is this different than getChar? (I get I don't understand something here but not sure what)
(Though yes, `f` also is a constant... it's just a constant that happens to be a function, which `getChar` is not.)
http://hackage.haskell.org/package/base-4.11.1.0/docs/Prelud...
It represents an interaction with the outside world that results in a Char when you perform it. It isn't a function because in Haskell functions must be pure (with no side effects).
data MyFun a = MyFun (() -> Char)
Technically they'd be right. f: () -> SomeType
which can be called via: f ()
There are examples of this in the Gluon book (http://gluon-lang.org/book/syntax-and-semantics.html). There are no syntax-related difficulties at all here. This is also how it's done in OCaml and related languages.(Note: This is (basically) useless in Haskell, since laziness makes this have the same semantics as a constant:
f :: SomeType
But since Gluon is strict, there's a pretty important difference between the two.)It's certainly ambiguous to outsiders, which is kind of the point of the discussion. Simpler would be something like Python: map(inc, [1, 2, 3]). No ambiguity.
So `map inc [1,2,3]` is the same as `(map inc) [1,2,3]`.
This is consistent with the fact that -> is right associative.
So in Haskell, we usually say
map :: (a -> b) -> [a] -> [b]
Which is usually interpreted as 'If you give me a function which can turn a's into b's, and then you give me a list of a's, I can give you a list of b's.'
But it is exactly equivalent if we write the type of map like this:
map :: (a -> b) -> ([a] -> [b])
Which I like to read as 'If you give me a function which can turn a's into b's, I can give you a function which turns a list of a's into a list of b's.
It's certainly the case that ML-style languages have a syntax that can be unfamiliar to outsiders, but I would disagree that the syntax is ambiguous. Indeed, Standard ML (another functional language) is one of the few languages to be formally specified.
Maths is more type-dependent in the absence of parentheses. E.g. sin cos⁻¹ x = sin(cos⁻¹(x)), but A cos⁻¹ x = A × cos⁻¹(x).
By "ambiguous" I mean to people looking at the code. Obviously it's unambiguous and well-defined to the computer.
While it's been over a decade since I first learnt this style of functional languages, I don't remember being confused by application syntax. It also helps that function application binds more tightly than anything else.
I don't think it's any great indictment that it is not immediately understandable to someone who does not know the rule. There will be lots of other things someone will not be able to understand in a Gluon program, unless they know Gluon. It's perfectly reasonable to expect someone to learn a programming language before they write code in it.
Yes, mathematical notation is also heavily overloaded, often abbreviated or "abused" and full of "puns".
There have been some attempts to "fix" this. Lambda calculus (the basis of most functional languages) could be seen as an attempt to do this. A more recent example is https://en.wikipedia.org/wiki/Structure_and_Interpretation_o...
That is a lot like saying: in the Python expression "5 - 4 - 1", how does the language know to subtract 4 from 5 first, and then subtract 1 from the result, rather than subtracting the 1 first?
The answer is: precedence rules. It's not ambiguous, though you might not know which order applies if you aren't familiar with the language. The same could be said of the syntax of any language, except perhaps for those in the Lisp family.
Functional languages generally don't try to be familiar to programmers who are used to other paradigms, because that compromises the elegance of the language (for example, mandatory parentheses for function calls and currying are an ugly mix). But once you learn a language in the ML family (Haskell, OCaml, etc.), they all have the same kind of familiarity that a Java programmer feels when they look at C#.
I understand that other languages make different choices regarding syntax and readability. This particular choice, while not inherently a bad choice, makes things more difficult for most people, and led to eximius's comment at the start of the thread:
> It makes code much more difficult to read
I agree that the "f x y z" syntax can look confusing/unfamiliar to people who haven't seen it before, whereas everyone knows how subtraction works. But it's worth learning because currying is an elegant way to simplify a language and make it more uniform: a function is just a map from a single argument to a single result. To compose functions, you only need the output type of the first function to match the input type of the other function. You can build a general function composition function that takes two functions and returns their composition. There is no arity to worry about. Parentheses should be used for grouping and not be overloaded to also indicate function calls.
Personally I'm really not a fan of relying on operator precedence. Even in that Python example I would write it `(5 - 4) - 1` to disambiguate. I have a few issues:
- Firstly, parsing combinations with varying precedence is something that computers are good at but people not so much, so I see no reason to force readers to juggle this sort of stuff.
- Second, mathematical precedence rules are overly complicated. Trying to support them can make a language overly complicated. Languages like Smalltalk prefer to remain simple, even if that "breaks" standard mathematical expressions https://en.wikipedia.org/wiki/Smalltalk#Expressions
- Finally, arithmetic is a tiny part of a programming language. There's no "standard" way to extend operator precedence to include, say, 3D rendering operations, or parser combinators, or attaching event handlers, etc. All we can do is give each operator a number, at which point we're just exacerbating the "humans aren't good at precedence tables" problem.
This has also been discussed elsewhere, e.g. at http://wiki.c2.com/?OperatorPrecedenceConsideredHarmful
PS: I prefer the `f a b c` function call syntax to `f(a, b, c)`, but prefer s-expressions to both. Scheme is really nice, but I don't want to live without currying and laziness by default. I have high hopes for https://lexi-lambda.github.io/hackett :)
Depending on how dense the code is, it isn't that bad. But it is another barrier to understanding.
For example, how do you read this?
f -4
In F#, whitespace is significant (and it's a function call); in OCaml, it's a subtraction. The idea of wrapping a negative number with parens is not necessarily obvious. (a b c d)
I agree, it’s much better with parens - the scope is immediately obvious.Even python have a syntaxically useless ":" before idented block that makes it easier to spot.