1
$\begingroup$

I’m designing a language which uses Prefix (Polish) notation when applying functions. However, I’ve run into a problem: I’m not sure how to distinguish between function calls and variable references, since add a b can be parsed as either add(a, b) or add(a(b)).

One solution I thought of is requiring variables holding functions to start with a lowercase letter and have regular variables start with an uppercase letter, or a similar solution. Functions in my language are all lambdas, which can either have named arguments or just state how many arguments they take and use $n to refer to the nth arg. The issue with my proposed solution is that it doesn’t really work with the latter syntax for defining functions. I also can’t just rely on the function’s arity (how many arguments it takes) because with the latter syntax, whatever arguments were passed to the function are implicitly passed down to whatever functions are called with too few parameters (e.g. {* 2} 5 becomes * 2 5)

Do you have any ideas as to how I might get around this? If there is a good solution in an existing language, that would be very helpful. I don’t want to bloat my syntax too much, but I’d rather have verbose but consistent code than concise but confusing.

$\endgroup$
0

3 Answers 3

4
$\begingroup$

Explicit Parentheses around arguments

This is how most languages with prefix functions does it (and the core of Lisps):

add(a b)
add(a(b))
{* 2}(5)
(add a b)
(add (a b))
({* 2} 5)
$\endgroup$
1
  • 1
    $\begingroup$ Accepting this answer because it shows an established syntax adding little overhead in most cases. $\endgroup$
    – Jacob
    Commented May 23, 2023 at 14:29
4
$\begingroup$

Polish notation ─ and reverse Polish notation ─ can only be unambiguous if the arity of every term is known by the parser. So add a b would be parsed as a and b (each of zero arity) being the arguments to the function add (of arity 2). Even so, this can make the language harder to read by humans, because it also requires the human to know the arity of each term in order to determine the program's meaning.

The only other way to resolve ambiguity ─ and this applies whether you just want to make the language more readable to humans, or you want terms to have dynamic arities (e.g. user-defined functions) ─ is to simply define in the language specification which of the two meanings an otherwise-ambiguous term has. For example, you can say that add a b always means a and b are the arguments to add, and provide a different syntax for writing it the other way. Typically, that means you use brackets. This is how functional languages like Haskell or F# work.

You mention in the question that you can't rely on using the arity of a term to parse it, because you want to support partial application. This isn't really a problem, though. You can have the parser know the arity of each term, but then if fewer terms are provided, the parser just consumes fewer terms and the function is partially-applied.

$\endgroup$
4
  • $\begingroup$ Good point about this still being possible with partial application, +1. But I think knowing the arity of the function at parse time will be too complicated with higher-order functions. $\endgroup$
    – Jacob
    Commented May 23, 2023 at 14:27
  • 1
    $\begingroup$ @Jacob Higher-order functions are still possible if their types are declared before they're used. The issue is that the parser needs to resolve names at parse-time, but it already does if any user-defined functions are involved. $\endgroup$
    – kaya3
    Commented May 23, 2023 at 14:29
  • $\begingroup$ That’s true, but I’m not yet sure whether this language will be statically or dynamically typed. $\endgroup$
    – Jacob
    Commented May 23, 2023 at 14:35
  • $\begingroup$ For an example of a statically typed language whose parser uses types to determine how many arguments each function takes, see Husk (it's a really fun golflang) $\endgroup$
    – user
    Commented May 23, 2023 at 18:46
4
$\begingroup$

Optional parens, but require commas

This is what CoffeeScript does:

foo bar baz   # JS: foo(bar(baz))
foo bar, baz  # JS: foo(bar, baz)
(foo bar) baz # JS: foo(bar)(baz)

Of course, this doesn’t work for four or more terms:

foo bar baz, qux
# Is this foo(bar(baz), qux) or foo(bar(baz, qux))?
# CoffeeScript goes with the latter; the former requires parens:
foo bar(baz), qux
$\endgroup$
1
  • $\begingroup$ I like this idea of doing it, but I’m accepting the answer of the Lisp syntax because it is pretty established and easy to parse in your mind in comparison to CoffeeScript’s approach. $\endgroup$
    – Jacob
    Commented May 23, 2023 at 14:34

You must log in to answer this question.

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