Opened 7 years ago

Last modified 6 months ago

#1444 new feature request

Template Haskell: add proper support for qualified names in non-splicing applications

Reported by: SamB Owned by:
Priority: normal Milestone:
Component: Template Haskell Version: 6.6.1
Keywords: Cc: SamB
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Difficulty: Unknown
Test Case: Blocked By:
Blocking: Related Tickets:

Description

For the uninitiated, Derive is an application of Template Haskell that can generate external Haskell code (depending on how you use it) instead of having it spliced into a module. Template Haskell was not designed for this, and it shows in the handling of qualified names. For example:

Prelude> '[]
GHC.Base.[]
Prelude> 'True
GHC.Base.True
Prelude> '(*)
GHC.Num.*
Prelude Data.Array.Unboxed> ''UArray
Data.Array.Base.UArray

It would be nice if Template Haskell could instead use public names, where available, so that Derive and similar tools would be able to use qualified names (and the quoting syntax without fear of their ending up pointing into the middle of no-mans-land, or even GHC-only land.

This would also mean that users of Derive via TH splicing wouldn't need to import so many modules that the derivings depend on.

Change History (18)

comment:1 Changed 7 years ago by simonpj

The trouble is that GHC has no idea what a "public name" might be. The name by which you refer to Data.Array.Base.UArray depends n the context you are in. But TH is supposed to be lexically scoped, so you must get Data.Array.Base.UArray regardless of context.

Perhpas Derive can generate import statements for any qualified names it mentions?

Simon

comment:2 Changed 7 years ago by neil

For something like [], we don't want to generate GHC.Base.[], we would want to generate Prelude.[], if anything. We may be able to have some notion of what a fixed list of names are when not in GHC, but it will never be complete.

I'm not sure if Derive should be messing with module imports, that's something that could go wrong quite easily. I'm not sure what fixes (if any) could be made to Template Haskell to make Derive style usage more natural.

Neil (Derive author)

comment:3 Changed 7 years ago by simonpj

But what if the prelude is not imported? Or what you are referring to something not imported by the Prelude?

I think what you want is a way to refer to the original nane of something. Something like

  module Foo where
    foo :: {-# ORIGINAL #-} Data.Array.Base.UArray
    foo = ...

Notice no imports! The binding of the TyCon? is completely fixed.

This is what TH does internally. See the Name data type in TH. You are trying to still make it possible, but in the form of Haskell source code.

Actually it's mainly a lexical/parsing problem. If you can decide on a lexical syntax for these original names, and persuade GHC to parse them, all subsequent phases know about original names, so it'd probably just work. See http://hackage.haskell.org/trac/ghc/wiki/Commentary/Compiler/RdrNameType (look at the Orig constructor for RdrName.

comment:4 Changed 7 years ago by SamB

Well, I was thinking that it would be nice to be able to specify a "cononical module" for a name, which might or might not correspond with where the Haddock documentation appears. I have no problem with needing to add a bunch of "import qualified"s in order to use Derive by non-TH means -- it seems better than having to guess at what modules to import, and having to worry about name clashes, as is currently necessary. And maybe UArray wasn't a good example ;-).

comment:5 Changed 7 years ago by SamB

Hmm, how would you use an Orig to refer to Prelude.map? Prelude is not the actual defining module of the standard map in GHC, but Prelude.map is the name Derive would want to print. That wikipage says, though, that the Module field is supposed to be hold defining module, not the exporting one.

comment:6 Changed 7 years ago by simonpj

You'd refer to GHC.Base.map or whereever map is actually defined.

The problem you are trying to solve is this: you want Derive to print out a Haskell program that mentions map and, when compiled, will get the right map. The only way I know to do that for sure is

a) print out GHC.Base.map and import GHC.Base (or whatever the right module is; Dervive, being a TH program, can find out easily.

b) or add some new lexical syntax so you can {-# ORIG #-} GHC.Base.map as I suggested earlier.

The advantage of (a) is that can do it right away, without waiting for some not-yet-designed feature to be implemented.

S

comment:7 Changed 7 years ago by igloo

I could be wrong, but I think the problem is that they want to generate *portable* Haskell code, so if map is defined in GHC.Base and re-exported by Prelude then they need to be told "Prelude.map".

Perhaps we could have something like this in GHC.Base:

{-# CANONICAL map Prelude #-}
map :: ...
map = ...

Then 'map would be Prelude.map, and we could also have :i tell us the canonical name:

:Prelude> :i map
map :: (a -> b) -> [a] -> [b]   -- Defined in GHC.Base, canonical location Prelude.map

It would probably make GHC's internals more complex, though, and with separate compilation it's also not obvious how to check it's actually correct.

comment:8 Changed 7 years ago by Isaac Dupree

CANONICAL would also mean, what if someone converted over to using some FixedPrelude? which also exports map, or believed map should only come from Data.List and never imported it from any Prelude... I don't think it's very good to add the notion of a canonical location to Haskell. (of course the notion it's replacing is physical-definition-location, another notion that isn't supposed to exist in Haskell :-P)

What if there is some separate (customizable? extensible?) file that is just a mapping from raw names to other names? Sequencing them could transform GHC.Base.map -> Prelude.map -> FixedPrelude?.map ... Seems possibly a maintenance nightmare for GHC developers at least...

If Derive knows exactly what module it's splicing into (meaning, what that module already imports), could it just look through all imported symbols until it finds one that refers to the same "map" (and fail if it can't find one)?

comment:9 Changed 7 years ago by neil

Derive has no knowledge of where it is being spliced, and indeed its not always being spliced, since it can be run as a preprocessor.

(It may just be me, but I'm not entirely clear exactly what this bug is asking for? Can someone post a single short example of current behaviour vs desired behaviour?)

comment:10 Changed 7 years ago by SamB

Well, the current behaviour is as shown above. I'm not quite sure what I think it should do, but it doesn't involve referring to GHC.* unnecessarily.

comment:11 Changed 7 years ago by SamB

One example that exhibits the problem is:

% (echo import qualified System.IO; ghc -fth -e 'let s = unlines [show n++" bottles of beer on the wall, "++show n++" bottles of beer, take one down, pass it around, "++show (n-1)++" bottles of beer." | n <- [100,99..1]] in Language.Haskell.TH.runQ [d| main = putStr s >> return () |] >>= (print . Language.Haskell.TH.ppr)') > beer.hs
% runhugs beer.hs
runhugs: Error occurred
ERROR "beer.hs":10275 - Syntax error in input (unexpected `)')

Yeah, I know that's a pretty long one-liner...

comment:12 follow-up: Changed 7 years ago by igloo

  • Milestone set to 6.1

Here's a simpler example which I think still has all the important bits:

$ (echo import qualified System.IO; ghc -fth -e 'Language.Haskell.TH.runQ [d| main = putStr "Foo" >> return () |] >>= (print . Language.Haskell.TH.ppr)')
import qualified System.IO
main = System.IO.putStr "Foo" GHC.Base.>> GHC.Base.return GHC.Base.()

So basically you want pprinting TH to generate portable code.

In general this is impossible as TH can dig out names that aren't exported.

Apart from that, something like a "canonical" name could work. A nicer alternative might be a way to ask, in the Q monad, which modules (either giving it a list of module Name's, or using the modules that the current module imports) export a given name.

This is quite close to #1480, actually.

comment:13 in reply to: ↑ 12 Changed 6 years ago by SamB

  • Cc SamB added

Replying to igloo:

So basically you want pprinting TH to generate portable code.

Well, it doesn't have to be pprint. But it would be nice to have a reasonable way...

In general this is impossible as TH can dig out names that aren't exported.

Well, obviously it won't be possible to print nonportable code into portable code... but that isn't the point at all ;-).

Apart from that, something like a "canonical" name could work. A nicer alternative might be a way to ask, in the Q monad, which modules (either giving it a list of module Name's, or using the modules that the current module imports) export a given name.

It would be nice if it were possible to trace how the name was imported...

comment:14 Changed 6 years ago by igloo

  • Milestone changed from 6.10 branch to _|_

comment:15 Changed 6 years ago by simonmar

  • Architecture changed from Unknown to Unknown/Multiple

comment:16 Changed 6 years ago by simonmar

  • Operating System changed from Unknown to Unknown/Multiple

comment:17 follow-up: Changed 15 months ago by morabbin

  • Type of failure set to None/Unknown

Bump: still relevant, given TH progress?

comment:18 in reply to: ↑ 17 Changed 6 months ago by errge

Yes, this is still relevant.

Here is igloo's comment actualized for 7.7:

(echo import qualified System.IO; ~/tmp/ghc/inplace/bin/ghc-stage2 -XTemplateHaskell -e 'Language.Haskell.TH.runQ [d| main = putStr "Foo" >> return () |] >>= (print . Language.Haskell.TH.ppr)')
import qualified System.IO
main_0 = System.IO.putStr "Foo" GHC.Base.>> GHC.Base.return GHC.Tuple.()

As you can see, the names are still "wrong".

There is progress on #1480, but we still need to add export list reification to that new ModuleInfo? datatype. Once that is done, the user will be able to go through the imported modules at least and find one that exports the given function and the module name is likable.

If this workaround is acceptable, then we can easily target 7.10 with this.

Note: See TracTickets for help on using tickets.