Opened 3 years ago

Last modified 3 years ago

#12249 new bug

Template Haskell top level scoping error

Reported by: simonpj Owned by:
Priority: normal Milestone:
Component: Template Haskell Version: 8.0.1
Keywords: Cc:
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

Description

Consider this

module TH where

  $( [d| x = True; f1 = x |])

  $( [d| x = 'c'; f2 = x |] )

I think this should compile fine. There are two bindings of x, so I would not expect to be able to name x in the export list, or indeed anywhere else. But x may just be a local helper-function to the other bindings (f1 and f2 resp) so that's fine.

Alas I get

~/5builds/HEAD-4/inplace/bin/ghc-stage2 -c TH.hs -ddump-splices -ddump-tc
TH.hs:5:4-26: Splicing declarations
    [d| x_apA = True
        f1_apB = x_apA |]
  ======>
    x_a3tN = True
    f1_a3tO = x_a3tN
TH.hs:7:4-25: Splicing declarations
    [d| x_a3u4 = 'c'
        f2_a3u5 = x_a3u4 |]
  ======>
    x_a3uW = 'c'
    f2_a3uX = x_a3uW

TH.hs:7:4: error:
    Multiple declarations of ‘x’
    Declared at: TH.hs:5:4
                 TH.hs:7:4

TH.hs:7:4: error:
    The exact Name ‘x_a3uW’ is not in scope
      Probable cause: you used a unique Template Haskell name (NameU), 
      perhaps via newName, but did not bind it
      If that's it, then -ddump-splices might be useful

So:

  • Perhaps we should not complain about multiple top-level bindings for x if they are all Exact RdrNames.
  • And the "not in scope" message is clearly bogus.

Mind you, would you expect this to work?

x = True

$( [d| x = 'c'; f2 = x |] )

foo = x  -- Which 'x' do we get?

Maybe the occurrence of x in foo is ambiguous. If you want to splice a hidden helper binding, which can't clash with some earlier or later user-supplied thing, then use an occurrence like $x.

Change History (7)

comment:1 Changed 3 years ago by goldfire

Perhaps the error message is regrettable, but otherwise, I like the original behavior. You say in your original example that x is not available for export or other use outside of the splices. But then why should f1 or f2 be? Is it only that they are unambiguous?

What about

$([d| x = 5 |])
y = x
$([d| x = 7 |])

That's unambiguous. But surely confusing!

Could you write a specification of your intended behavior?

comment:2 Changed 3 years ago by simonpj

Yes, it's only that they are unambiguous.

Could you write a specification of your intended behavior?

Yes. Occurrences are OK if they are unambiguous. That's it really.

comment:3 Changed 3 years ago by goldfire

OK. But why do we want this behavior? Would it simplify the implementation?

comment:4 Changed 3 years ago by simonpj

But why do we want this behavior?

Consider the code in the Description. It seems perfectly reasonable to me; the two quasiquotes both bind 'x' but in each case it's clear what binds the occurrence of x on the RHS of f1, f2. I'm thinking of splices that generate a wad of code that needs some auxiliary bindings; we don't want to have to worry about a clash if we happen to re-use the same name when generating the auxiliary bindings in a different splice.

For example, deriving code. In fact it's this that is causing #12245, although that code is generated by GHC itself, not by TH, but it's the same idea.

comment:5 Changed 3 years ago by goldfire

Hm. I suppose I've run into the same issue when writing singletons. But isn't this what newName is for? Currently, newName doesn't always do a good enough job at creating a top-level name that's fresh even between splices, but I'm sure we could fix it.

In effect, you're trying to turn TH into a module/namespace system, where some apparently-top-level declarations actually have a limited scope. Whether or not a definition is local or global depends on the entire file, instead of being listed in one spot. While what you propose may be readily implementable, I would say that it may be time better spent designing a proper module/namespace system that TH could use.

As you've designed this feature, I could see people (perhaps even including myself) using TH only for your new module-like capabilities. For example, when writing papers, I often use the same variable names in different examples. When I extract my code and compile the papers, these overlapping variable names cause annoyance. If you implement your feature, then I would put some TH dingbats in my extraction engine to avoid the ambiguous variable uses/redefinitions... but this is abusive of TH. Clearly, compiling academic papers is not the primary use-case for Haskell (or is it? I suppose it once was!) but I'm sure I'm not the only one who would do such shenanigans.

comment:6 Changed 3 years ago by simonpj

But isn't this what newName is for?

Well, no. newName is a last resort when quasiquotation won't work. Quasiquotes are the preferred way of creating lexically scoped binders.

And even newName will create a new name with distinct unique but the same occurrence name, which will give the same "Two top level bindings for x" error.

I don't think I'm trying to build a new module/namespace system. In Haskell 2010, it's ok to import two different functions both called f provided you don't refer to f unqualified. It's the same here. If I mention x I expect a complaint about ambiguity; but if I don't I expect it to be fine.

comment:7 Changed 3 years ago by goldfire

Your last comment suggests a way to think about all of this: Each declaration group (as defined in the manual) is its own unnamed module that exports all of its symbols. That actually might not be a bad way of implementing it either. (And newName identifiers should never be exported, as per discussion in comment:13:ticket:10599, yet to be finished.) In any case, I still say you're building some sort of ad-hoc implicit module system.

Note: See TracTickets for help on using tickets.