Fractional/negative fixity?

Jón Fairbairn jon.fairbairn at cl.cam.ac.uk
Wed Nov 8 11:11:02 EST 2006


Simon Marlow <simonmar at microsoft.com> writes:

> Nicolas Frisby wrote:
> > Let's remember that if something is broke, it's only _right_ to _fix_
> > it. I patiently waited for someone else to make that pun.
> >
> > Understanding the language won't be much harder, but understanding
> > fixity declarations will become a task. Consider:
> >
> > infixl -1.7521  -- what and why?
> >
> > As the operator space becomes more dense, negative and fractional
> > fixities are going to become more obfuscated. The negative and
> > fractional fixities will satisfy a number purposes well, but they will
> > also be abused and lead to confusion.
> >
> > This smells like a wart growing on a wart to me.
> 
> All these are valid points.  However, given that we can't
> completely redesign, implement and test a new fixity
> system in time for Haskell',

...the correct thing to do is to leave it alone, rather than
make a change that addresses only one of the problems.

> it makes sense to make a simple change that unambiguously
> improves the current system,

I dispute that. It does make it possible to introduce a new
operator between two others, but on its own, that strikes me
as as likely to be a new problem as an improvement because
of the difficulty of remembering numeric precedences. It's
bad enough with the present number, let alone a countable
infinity of them.

The biggest flaw in the present system (and something I
wanted to address in my original proposal way back when) is
that there is no way to state that there is /no/ precedence
relationship between two operators. It would be far better
to have the compiler give an error message saying that an
expression needs some parentheses than have it choose the
wrong parse.

The next smaller flaw is that numeric precedences are a poor
match for the way we think.  I can easily remember that (*)
binds more tightly than (+), or that (+) beats (:) (though
the latter is slightly less obviously correct), but I don't
remember the numbers so when I want to define something new
that has a similar precedence to (*) (some new kind of
multiplication), I have to look it up, which is tedious.

Wanting to insert an operator between two others comes lower
in importance even than that, because in many cases giving
it the same precedence as something and appropriate
associativity gets you most of the way there.  It bites
because you can't say you want an error if you use it next
to something else without parentheses.

Let me throw out a couple of possibilities differing only in
syntax (one of my fears is that if we get fractional
fixities the other problems will be forgotten, so a real
improvement will never be discussed).  I don't expect either
of them to go into Haskell', but putting them forward might
encourage further discussion and discourage introduction of
something "temporary" that will stay with us forever.  

Syntax 1, based on Phil Wadler's improvement of my old
proposal. The precedence relation is a preorder.

infix {ops_1; ops_2; ...; ops_n}

(where each ops is a collection of operators optionally
annotated with L or R) would mean that each operator in
ops_i binds more tightly than all the operators in ops_j for
j>i. (and we require ops_i `intersect` ops_j = empty_set for
i/=j) Layout rule applies for {;...}. An op can be a varsym
or a backquoted varid.

It says nothing about the relationship between the those
operators and operators not mentioned, except by virtue of
transitivity. So

infix R ^
      L * /
      L + -

would replicate the current relationships between those
arithmetic operators. An additional declaration

infix +
      R :

says that (+) binds more tightly than (:) and by
transitivity, so do (^ * and /). The associativity label
obviously has to be the same for all occurrences of an
operator in scope, so omitting it just says that it's
specified elsewhere.

infix *
      R @+ @-
      +

says that (@+) and (@-) fall between (*) and (-), and that
(a @+ b @- c) parses as (a @+ (b at -c)) but

infix * 
      R @@

says that (a * b @@ c @@ d) parses as ((a*b) @@ (c@@d)) but
leaves (a + b @@ c) undefined (a compile time error) unless
another declaration specifies it elsewhere. And

infix R @@ @@@

says nothing about the relationship between @@ or @@@ and
other operators, but indicates that they associate to the
right individually and together.


The alternative syntax is exemplified thus:

infix L + - (L * / (R ^))

The precedence increases the more deeply you go into the
parentheses. Arguably this is more suggestive and avoids the
possibility of reading precedences as increasing down the
page (danger of endianism argument cropping up there!), but
may be harder to read.

With both syntaxes there's no reason to reserve L and R,
since the context disambiguates.

For exports (imports) you pass the graph of the relation
with the unexported (unimported) operators omitted.

> and is no more difficult to implement (in fact, I bet it
> adds zero lines of code to the compiler).

If ease of implementation had been a criterion, we'd never
have had Haskell at all!

I don't think the above suggestion would be hard to
implement -- for someone who knows the internals of a
compiler, anyway.


  Jón



More information about the Haskell-prime mailing list