[Haskell-cafe] Type Directed Name Resolution

Mark Lentczner markl at glyphic.com
Fri Nov 12 01:13:20 EST 2010

My tuppence:

I feel like the main impetus for TDNR is the awkwardness of records, especially when there are multiple record types within a module (as there often are). Now, if one proceeds as one has to today, then one may find:

data Foo = Foo { fooName :: String, fooValue :: Double }
data Bar = Bar { barName :: String, barValue :: [String], barSubbars :: [Bar] }

Let us, for sake of argument, ignore that perhaps fooName and barName represent the same semantic concept and these should all somehow be refactored. 

I suspect that the prime annoyance, the thing that makes us yearn for our old C/C++/Java/Python ways, is the tediousness of having to prefix all the field names with "foo" or "bar". Especially when the data type name is large, one ends up having to invent coding conventions one doesn't want to:

data ExecutionTraceSummary = ExecutionTraceSummary { etsStart :: Time; ... }

So imagine that we take the tedium out of typing all those prefixes by anointing some initial character, say the apostrophe, as a sort of name mangler:

data Foo = Foo { 'name :: String, 'value :: Double }
data Bar = Bar { 'name :: String, 'value :: [String], 'subbars :: [bar] }
data ExecutionTraceSummary = ExecutionTraceSummary { 'start :: Time, ... }

Now, to use them, perhaps we have to explicitly write the full form:

showFoo :: Foo -> String
showFoo f = Foo'name f ++ "(" ++ show (Foo'value f) ++ ")"

We could allow a form of shortened local reference by allowing the full form to "flow through" type declarations:

type ETS = ExecutionTraceSummary

logExecutionTraceSummary :: ExecutionTraceSummary -> IO ()
logExecutionTraceSummary s = do
    putStr $ ETS'start s

Mind you, I realize that apostrophe may not work here, and details haven't been worked out.

[...that was the first pence, here comes the second...]

If you buy any of that, then one could allow, in the manner pointed out by some (though in particular I'm thinking of David Menendez's example), that this construction could imply a type class and associated type. That is, the first appearance of 'name in a record implies this:

class <C>'name a where
  type <R>'name a :: *
  'name :: a -> <R>'name a

and for each appearance of 'name :: X as a field of Foo:

instance <C>'name Foo where
  type <R>'name Foo = X
  'name = Foo'name

(Here, <C> and <R> are some unwritable prefixes used by the compiler. It remains to be seen if these should be module scoped or program global.)

So, in the case (repeated from above):

data Foo = Foo { 'name :: String, 'value :: Double }
data Bar = Bar { 'name :: String, 'value :: [String], 'subbars :: [Bar] }

We get auto generated:

class <C>'name a where
  type <R>'name a :: *
  'name :: a -> <R>'name a

class <C>'value a where
  type <R>'value a :: *
  'value :: a -> <R>'value a

class <C>'subbars a where
  type <R>'subbars a :: *
  'subbars :: a -> <R>'subbars a

instance <C>'name Foo where
  type <R>'name Foo = String
  'name = Foo'name

instance <C>'name Bar where
  type <R>'name Bar = String
  'name = Bar'name

instance <C>'value Foo where
  type <R>'value Foo = Double
  'value = Foo'value

instance <C>'value Bar where
  type <R>'value Bar = [String]
  'value = Bar'value

instance <C>'subbars Bar where
  type <R>'subbars Bar = [Bar]
  'subbars = Bar'subbars

*Now* one can write:

showFoo :: Foo -> String
showFoo f = 'name f ++ "(" ++ show ('value f) ++ ")"

nameBoth :: Foo -> Bar -> String
nameBoth f b = 'name f ++ " " ++ 'name b

None of this requires any more type machinery than already exists with TypeFamilies. It perhaps suffer some of the prior objections to implying semantic equivalence (in say the 'value fields) where none exists. But, it is limited in scope to fields, and only when one uses the special naming sigil (apostrophe here).

Of course, this approach would meld nicely with any better record/label system.  For starters:

class <C>'name a where
  type <R>'name a :: *
  'name :: a -> <R>'name a
  ''name :: <R>'name a -> a -> a

instance <C>'name Foo where
  type <R>'name Foo = String
  'name = Foo'name
  ''name = \v x -> x { Foo'name = v }

There now -- I feel like TNDR is placating the muscle memory in our fingers that wants to type "f.name" ... and I hope we find a solution to replacing the tedium of so many "fooName" fields, and perhaps solve the record update ugliness as well!

- Mark

Mark Lentczner
IRC: mtnviewmark

More information about the Haskell-Cafe mailing list