[bitc-dev] Quick haskell question

Mark P. Jones mpj at cs.pdx.edu
Fri Aug 13 15:04:05 PDT 2010


> On Thu, Aug 12, 2010 at 11:53 AM, Mark P. Jones <mpj at cs.pdx.edu> wrote:
> Fixity statements in Haskell are lexically
> scoped, but that doesn't ... guarantee or
> require that you have seen a fixity declaration before you see a use.
>  
> This does not correspond to lexical scoping as I understand the term. In a lexical scoping (disregarding imports), the thing used must be introduced somewhere up and to the left of the site of use. I'm aware of some weird special cases that arise in things like object scopes in C++, but strictly speaking, such scopes aren't lexical scopes.

I'm not sure it's productive to debate the meaning of a fairly
standard term like "lexical scoping", but it seems clear that
you and I currently understand this term in different ways.

To me, lexical scoping implies that you can move from the use
of an entity to the corresponding definition/binding/introduction
of that entity just by looking at the program.  This contrasts
with "dynamic scoping" in which the definition/etc.. for a
given use can vary depending on the environment in which it is
executed.  In the following program, for example, we get an
output of 1 with lexical scoping or 2 with dynamic scoping.

   x := 1
   f := \y -> y + x
   x := 2
   print (f 0)

I can see how this might appear to tie in with the "left of
the site of use" idea that you mentioned, but that's just an
incidental detail, not an intrinsic part of the definition.
If "left of the site of use" was part of the definition, then
you'd have to disqualify both Haskell and Java from being
lexically scoped because both allow implicit forward
references.

> Perhaps my question should have been: can you point to an example case where the use of an infix operator truly wants to precede the identificaiton of that operator as infix?

I'm not sure I can provide a compelling example to satisfy you.
At some level, all of this fixity stuff is just about syntactic
sugar, and it is always going to be subjective.

The classic use case might be a set of mutually recursive
definitions like the following pseudo code where a programmer
wants to put fixity declarations in the same place as the rest of
the code for the corresponding symbol.

   infixr 6 <->
   (<->)  :: some type goes here
   x <-> y = some definition involving <*>

   infixr 7 <*>
   (<*>)  :: some other type here
   x <*> y = some other definition, this time involving <->

Of course, it is not strictly necessary to do this---we could just
insist that all fixity declarations appear at the very beginning
of the file, which is exactly what early versions of Haskell did.
But some programmers still felt that they had good aesthetic
reasons for laying their code out as above.  And eventually, at
the same time that the definition of Haskell was revised to allow
local fixity declarations, they got their wish.  To me, however,
the best part in all of this was the fact that it allowed me to
clean up my implementation; I don't think it was harder or
required more code, but it encouraged me to remove clunk special
cases and replace it with clearer, more general code.  I wouldn't
go back, even if I never used the new flexibility it provided.

But let's suppose you want to stick with "declare before use" for
now (after all, like Haskell, you could always switch to the more
liberal approach later), and that you still want to support local
fixity declarations.  In that case, there might still be some
sticky details to deal with if you rely on lexical analysis to
attach fixity information to symbols.  Here are a couple of
examples to hint at the issues here:

    infixr 8 <*>

    f x = ... something involving <*> ...
     where infix 7 <*>   -- oops, fixity after use
           x <*> y = ...

    h z = ... something involving <*> ...
         -- be sure to use the infixr 8 fixity here
         -- and not the more recent infix 7 that was
         -- valid only in the definition of f.

In my opinion, you really don't want to write a lexer that
has to track "let"s and "in"s and maintain a stack of
fixity bindings, just to handle code like this.  Such tasks
are much better handled during static analysis where you
have easy access to environment structures and few to no
left-to-right constraints.

Maybe you don't buy the need for local fixity declarations
at all?  I'd be pretty much with you on that, having never
used them myself as far as I can recall (except in test
programs).  Their saving grace, as far as I'm concerned,
is that they allow you to move top-level code into a local
definition (as part of a refactoring, for example) without
having to add extra parens, remove fixity decls, etc...
(That and the fact that, once you've decided to break away
from "declare fixity before use" at the top level, then it
is just as easy to use the same code to handle fixity decls
in local bindings.)

All the best,
Mark




More information about the bitc-dev mailing list