[Haskell-cafe] Some thoughts on Type-Directed Name Resolution

AntC anthony_clayden at clear.net.nz
Fri Feb 3 10:30:33 CET 2012


Kevin Quick <quick <at> sparq.org> writes:

> 
> > Currently under H98:
> >        f.g    -- (both lower case, no space around the dot)
> > Is taken as function composition -- same as (f . g).
> >        f.  g  -- is taken as func composition (f . g)
> >        f  .g  -- is taken as func composition (f . g)
> 
> And so it is.  Could have sworn these weren't accepted, but clearly I'm  
> wrong.  Thanks for pointing this out.
> 

On a bit more digging, I'm scaring myself. These are both valid (H98):

     Data.Char.toUpper.Prelude.head.Prelude.tail $ "hello"   -- Strewth!
     "hello".$Prelude.tail.$Prelude.head.$Data.Char.toUpper
                         -- using (.$) = flip ($) as fake dot notation
GHCiorHugs==> 'E'

The first example is good in that you can mix qualified names in with dot 
notation, and the lexer can bind the module name tighter than dot-as-function-
composition.

It's bad that not only are we proposing changing the meaning of dot, we're 
also changing the direction it binds. If you put in the parens:

     (Data.Char.toUpper.(Prelude.head.(Prelude.tail)))  "hello"
     (("hello".$Prelude.tail).$Prelude.head).$Data.Char.toUpper

Or perhaps not so bad, left-to-right thinking?

Another syntax change about dot-notation is that it binds tighter **than even 
function application**:

      map toUpper customer.lastName

Desugars to:

      map toUpper (lastName customer)

Compare if that dot were function composition:

      (map toUpper customer) . lastName    -- of course this isn't type-valid


But wait! there's more! we can make it worse! A field selector is just a 
function, so I can select a field and apply a function all in one string of 
dots:

      customer.lastName.tail.head.toUpper   -- Yay!!

> 
> I was trying to ... *but* also  
> indicate that I specifically want the field selector rather than some  
> arbitrary f.  I wanted to extract the field f of every record in recs but  
> clearly indicate that f was a field selector and not a free function.
> 
> And this is finally our difference.  I had wanted the no-space preceeding  
> dot syntax (.f) to specifically indicate I was selecting a field.  ...

You seem to be not alone in wanting some special syntax for applying field 
selectors (see other posts on this thread). H98 field selectors don't do this, 
they're just functions.

And there's me bending over backwards to make all Type-Directed overloaded-
Name Resolution field selectors just functions, so you can mix field selectors 
and functions **without** special syntax. Example Yay!! above.

I'm puzzled why you want different syntax for field selectors. Can you give 
some intuition?

Of course you can adopt a convention in your own code that dot-notation is for 
field selection only. (But you can't legislate for code you're importing.) 
(And Donn Cave wants to be able to ignore dot notation all together.)

AFAIC OO languages lets you put all sorts of weird stuff together with dot 
notation. SPJ's got an example from Java in his TDNR.

I hope it's not because you name your fields and functions with brief, 
cryptic, one-letter codes!! You do have a coding convention in you production 
code to use long_and_meaningful_names, don't you?!

So you can tell `customer' is a customer (record), and `lastName' is a last 
Name (field), etc.


>  The issue can  
> be resolved by explicit module namespace notation (ala. Prelude.map v.s.  
> Data.List.map).

I want module namespace notation **as well as** dot notation. This is my 
import from a distant planet example. And it'll work, going by example 
Strewth! above.

> 
> In addition, under SORF, SPJ indicated that "Dot notation must work in  
> cascades (left-associatively), and with an expression to the left:
>    r.x
>    r.x.y
>    (foo v).y
> "
> I assume DORF would also support this as well and that "r.x.y.z" would  
> desugar to "z (y (x r))".

Yes, as per discussion above.

> 
> With regards to module namespace notation, neither SORF nor DORF mentions  
> anything that I found, but I'm assuming that the assertion is that it's  
> not needed because of the type-directed resolution.

It's rather the other way round. We want to avoid qualified names, and type-
directed resolution is the mechanism to achieve that ...

Where this 'Records in Haskell' thread started is that currently if you want 
to have the same field name in different records, you have to declare the 
records in different modules, then import them to the same place, and still 
you can only refer to them by putting the module prefix. (Unless you use the -
XDisambiguateRecordFields flag, but this only works within the scope of 
pattern matches and explicit record/data constructors; it doesn't work for the 
free-floating selector functions.)

And on balance, putting module prefixes everywhere is just too cumbersome.

So yes, the plan with SORF and DORF is that you can (mostly) use un-qualified 
names, and the resolution mechanism figures out which record type you're 
talking about.

One difference between DORF and SORF is that I want the resolution mechanism 
to be exactly class/instance resolution.

In contrast, both SORF and TDNR want some special syntax-level resolution for 
dot-notation, at the desugaring stage. I've re-read those sections in both 
proposals, and I still don't 'get' it. That's again what prompted me to try 
harder. I think I've ended up with an approach that's more 'Haskelly' in that 
the field selector is just an overloaded function, and we're familiar with 
them, and how they get resolved through type/instance inference. [I've just re-
read that last sentence: I'm claiming to be more 'Haskelly' than SPJ!! The 
arrogance!]

There's one further difference between DORF and SORF/TDNR. I'm explicit about 
this, but I'm not sure what SORF's take is. I think SORF/TDNR keeps with 
current Haskell that you can't declare more than one record with the same 
field name in the same module.

I want to declare many records in the same module with the same field name(s). 
This is my customer_id example: All three of the records for customer 
Name/Address, customer pricing, and customer orders have a customer_id field.


>  To wit:
> 
> Rlib/Recdef.hs:
> >     module Rlib.Recdef (R(..)) where
> >
> >     data Rec = R { foo :: String } deriving Show
> 
> Rlib/Rong.hs:
> >     module Rong (T(..)) where
> >     import Rlib.Recdef
> >     data Rstuff = T { baz :: R }
> >
> >     foo :: Rec -> String
> >     foo = show
> 
> main.hs:
> >     import Rlib.Recdef
> >     import Rlib.Rong
> >     main = let r = R "hi"
> >                t = T r
> >                bar, bar_pf :: Rstuff -> String
> >                bar_pf = Rlib.Recdef.foo . Rlib.Rong.baz
> >                bar x = x.baz.foo
> >            in assert $ bar_pf t == bar t
> >               assert $ Rlib.Rong.foo r /= Rlib.Recdef.foo r
> 
> The assumptions are that the syntax of bar and bar_pf would be the same  
> for both SORF and DORF, and that no namespace qualifiers are needed (or  
> allowed) for bar  (i.e. you wouldn't write something like "bar x =  
> x.Rlib.Rong.baz.Rlib.Recdef.foo").

This isn't really demonstrating the point. Both definitions of foo are 
monomorphic Rec -> String, there's no type-level difference. It's _not_ an 
overloaded definition of a single foo, it's a clash of names declared in 
different modules. So to tell them apart within the same scope, you always 
need the module qualifier.

The use of foo embedded in bar_pf is qualified, so bar_pf will always show the 
foo field within the record ("hi").

The foo in bar is not qualified. I'd expect the compiler to complain that it's 
ambigous. (Looks like that's valid code today, if you change bar's RHS to 
x.$baz.$foo -- did you try it?)

And no, you can't concoct an example today that demonstrates DORF, because 
record Rec automatically declares function foo with a monomorphic type. You'll 
have to create some shadow field/functions (foo_, _foo, Proxy_foo, the Has 
instance and all the drama) as I did in the RHCT post.


> 
> Apologies for putting you through the syntax grinder, and especially when  
> I'm not really qualified to be operating said grinder.  I know it's not  
> the interesting part of the work, but it's still a part.
> 
> Thanks, Anthony!
> 
> -Kevin
> 

Cheers
Anthony






More information about the Haskell-Cafe mailing list