6
votes

One big reason prefix operators are nice is that they can avoid the need for parentheses so that + - 10 1 2 unambiguously means (10 - 1) + 2. The infix expression becomes ambiguous if parens are dropped, which you can do away with by having certain precedence rules but that's messy, blah, blah, blah.

I'd like to make Haskell use prefix operations but the only way I've seen to do that sort of trades away the gains made by getting rid of parentheses.

Prelude> (-) 10 1 

uses two parens.

It gets even worse when you try to compose functions because

Prelude> (+) (-) 10 1 2 

yields an error, I believe because it's trying to feed the minus operation into the plus operations rather than first evaluating the minus and then feeding--so now you need even more parens!

Is there a way to make Haskell intelligently evaluate prefix notation? I think if I made functions like

Prelude> let p x y = x+y
Prelude> let m x y = x-y

I would recover the initial gains on fewer parens but function composition would still be a problem. If there's a clever way to join this with $ notation to make it behave at least close to how I want, I'm not seeing it. If there's a totally different strategy available I'd appreciate hearing it.

I tried reproducing what the accepted answer did here:

Haskell: get rid of parentheses in liftM2

but in both a Prelude console and a Haskell script, the import command didn't work. And besides, this is more advanced Haskell than I'm able to understand, so I was hoping there might be some other simpler solution anyway before I do the heavy lifting to investigate whatever this is doing.

1
This can’t really be done and isn’t really necessary. Use more lets to make expressions more readable. - Ry-
For (+) (-) 10 1 2 to work, you would need to lookup how many arguments (-) and (+) expect before being able to parse. Compared to needing to know fixities, this seems a bit more painful. Oh, and this would create ambiguities with unary -. - Alec
Why on earth do you want to use prefix notation in a language which was not designed for it? Even if it were somehow possible (e.g. through quasiquotation or TH) it would only lead to terribly unidiomatic code. - chi
@Ben I get that compliment a lot: "Your question isn't terrible." :) (joking) - Addem
I'm too lazy to expand it into an answer (someone else feel free) but look at Chris Okasaki's Flattening Combinators: Surviving without Parentheses. - Derek Elkins left SE

1 Answers

6
votes

It gets even worse when you try to compose functions because

Prelude> (+) (-) 10 1 2 

yields an error, I believe because it's trying to feed the minus operation into the plus operations rather than first evaluating the minus and then feeding--so now you need even more parens!

Here you raise exactly the key issue that's a blocker for getting what you want in Haskell.

The prefix notation you're talking about is unambiguous for basic arithmetic operations (more generally, for any set of functions of statically known arity). But you have to know that + and - each accept 2 arguments for + - 10 1 2 to be unambiguously resolved as +(-(10, 1), 2) (where I've explicit argument lists to denote every call).

But ignoring the specific meaning of + and -, the first function taking the second function as an argument is a perfectly reasonable interpretation! For Haskell rather than arithmetic we need to support higher order functions like map. You would want not not x has to turn into not(not(x)), but map not x has to turn into map(not, x).

And what if I had f g x? How is that supposed to work? Do I need to know what f and g are bound to so that I know whether it's a case like not not x or a case like map not x, just to know how to parse the call structure? Even assuming I have all the code available to inspect, how am I supposed to figure out what things are bound to if I can't know what the call structure of any expression is?

You'd end up needing to invent disambiguation syntax like map (not) x, wrapping not in parentheses to disable it's ability to act like an arity-1 function (much like Haskell's actual syntax lets you wrap operators in parentheses to disable their ability to act like an infix operator). Or use the fact that all Haskell functions are arity-1, but then you have to write (map not) x and your arithmetic example has to look like (+ ((- 10) 1)) 2. Back to the parentheses!

The truth is that the prefix notation you're proposing isn't unambiguous. Haskell's normal function syntax (without operators) is; the rule is you always interpret a sequence of terms like foo bar baz qux etc as ((((foo) bar) baz) qux) etc (where each of foo, bar, etc can be an identifier or a sub-term in parentheses). You use parentheses not to disambiguate that rule, but to group terms to impose a different call structure than that hard rule would give you.

Infix operators do complicate that rule, and they are ambiguous without knowing something about the operators involved (their precedence and associativity, which unlike arity is associated with the name not the actual value referred to). Those complications were added to help make the code easier to understand; particularly for the arithmetic conventions most programmers are already familiar with (that + is lower precedence than *, etc).

If you don't like the additional burden of having to memorise the precedence and associativity of operators (not an unreasonable position), you are free to use a notation that is unambiguous without needing precedence rules, but it has to be Haskell's prefix notation, not Polish prefix notation. And whatever syntactic convention you're using, in any language, you'll always have to use something like parentheses to indicate grouping where the call structure you need is different from what the standard convention would indicate. So:

(+) ((-) 10 1) 2

Or:

plus (minus 10 1) 2

if you define non-operator function names.