Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom naming hooks for autogenerated code #204

Closed
RyanGlScott opened this issue Jun 18, 2017 · 8 comments · Fixed by #427
Closed

Allow custom naming hooks for autogenerated code #204

RyanGlScott opened this issue Jun 18, 2017 · 8 comments · Fixed by #427

Comments

@RyanGlScott
Copy link
Collaborator

I ran into yet another naming conflict when using singletons recently:

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
module Bug where

import Data.Singletons.TH

$(singletons [d|
      data Ratio1 a = a :%  a
      data Ratio2 a = a :%% a
    |])

Compiling this with singletons-2.3 yields the following (abridged) -ddump-splices output and error message:

GHCi, version 8.2.0.20170523: http://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /home/rgscott/.ghci
[1 of 1] Compiling Bug              ( Bug.hs, interpreted )
Bug.hs:(10,3)-(13,6): Splicing declarations
    singletons
      [d| data Ratio1_a3DY a_a3E1 = a_a3E1 :%_a3DZ a_a3E1
          data Ratio2_a3DW a_a3E0 = a_a3E0 :%%_a3DX a_a3E0 |]
  ======>
    data Ratio1_a6oe a_a6oi = a_a6oi :%_a6of a_a6oi
    data Ratio2_a6og a_a6oj = a_a6oj :%%_a6oh a_a6oj
    data instance Sing (z_a6oA :: Ratio1_a6oe a_a6oi)
      = forall (n_a6oB :: a_a6oi)
               (n_a6oC :: a_a6oi). z_a6oA ~ (:%_a6of) n_a6oB n_a6oC =>
        (:%%) (Sing (n_a6oB :: a_a6oi)) (Sing (n_a6oC :: a_a6oi))
    data instance Sing (z_a6oJ :: Ratio2_a6og a_a6oj)
      = forall (n_a6oK :: a_a6oj)
               (n_a6oL :: a_a6oj). z_a6oJ ~ (:%%_a6oh) n_a6oK n_a6oL =>
        (:%%%) (Sing (n_a6oK :: a_a6oj)) (Sing (n_a6oL :: a_a6oj))

Bug.hs:10:3: error:
    Multiple declarations of ‘:%%’
    Declared at: Bug.hs:10:3
                 Bug.hs:10:3
   |
10 | $(singletons [d|
   |   ^^^^^^^^^^^^^^...

Urk. We have a conflict between the Ratio2 constructor and the singleton constructor for Ratio1. We could conceivably change the rules for generating singleton constructor names so as to avoid this problem, but I struggle to think of something that wouldn't create the potential for ambiguity somewhere else.

Instead of finagling the name generation rules, a better solution would be to allow users finer control over what gets generated. In the spirit of Data.Aeson.TH, we could offer an Options datatype (name subject to bikeshedding) that bundles hooks for generating Names. Something like:

data Options = Options
  { typeOptions :: TypeOptions
  , functionOptions :: FunctionOptions
  }

data DatatypeOrClass = IsDatatype | IsClass

data TypeOptions = TypeOptions
  { promotedKind :: DatatypeOrClass -> Name -> Name
  , singletonType :: DatatypeOrClass -> Name -> Name
  }

data ConstructorOrValue = IsConstructor | IsValue

data FunctionOptions = FunctionOptions
  { promotedType :: ConstructorOrValue -> Name -> Name
  , singletonFunction :: ConstructorOrValue -> Name -> Name
  , defunctionalizationSymbol :: ConstructorOrValue -> Int -> Name -> Name
  }

And then we could provide variants of singletons, promote, etc. that take Options as arguments.

@goldfirere
Copy link
Owner

I'm happy enough with an Options approach, provided it doesn't clutter the common case.

@RyanGlScott
Copy link
Collaborator Author

Indeed, how should we design Options such that it doesn't add clutter? I pondered having variants of singletons, promote, etc. that take Options as arguments, but it turns out that there are a lot of variants we would need to add to make this work. I count at least 37 such functions here, so adding a variant of each function that accepts Options would bring us to 74. Yikes.

An alternative design would be to thread Options through to the various TH functions mtl-style. I'm thinking something like:

data Options = ...

defaultOptions :: Options

class DsMonad m => SingMonad m where
  getOptions :: m Options

instance SingMonad Q where
  getOptions = pure DefaultOptions

newtype SingM m a = SingM (ReaderT Options m a)

withOptions :: Options -> SingM m a -> m a
withOptions opts (SingM x) = runReaderT x opts

instance SingMonad (SingM m) where
  getOptions = SingM ask

(If you have a better name ideas for SingMonad/SingM, I'm all ears.)

With this design, if one wishes to run the TH machinery with a custom set of options, all they have to do is this:

$(withOptions myCustomOptions $ genSingletons [''Foo, ''Bar])

Moreover, the SingMonad Q instance ensures that most existing splices (e.g., $(genSingletons [''Foo, ''Bar])) continue to work. Of course, this would technically be a breaking change since genSingletons would have to require a SingMonad context instead of just DsMonad, but this wouldn't be too difficult to adjust to.

@goldfirere
Copy link
Owner

That design is plausible. We could also imagine allowing users to write a singletonsOptions variable that sets the options. singletons gets this via reification. Except that reification doesn't give you definitions of terms. So it might have to be a type-level structure, fittingly.

Nothing wrong with your design -- just adding another option.

@RyanGlScott
Copy link
Collaborator Author

I'm not sure that I understand your design fully. Can you provide more details on how this would look from a users' standpoint?

@goldfirere
Copy link
Owner

type SingletonsOptions = NameGenerator "mangleNames" `And` OtherOptions `And` YetMoreOptions

$(singletons [d| ... |])

Reification could find the in-scope set of options.

@RyanGlScott
Copy link
Collaborator Author

If I understand you correctly, the code in singletons' TH machinery would attempt to reify something with the exact name SingletonsOptions? If so, I don't know if I'm a fan of the idea, since it would make it rather awkward to use different options across multiple calls to $(singletons ...) within the same module.

@goldfirere
Copy link
Owner

See ghc-proposals/ghc-proposals#283

But in reality, I tend to agree with you. I just thought it was a cute idea.

RyanGlScott added a commit that referenced this issue Dec 21, 2019
This patch introduces an `Options` data type and an `mtl`-like
`OptionsMonad` class for monads that carry `Options`. At present,
the only things one can do with `Options` are:

* Toggle the generation of `SingKind` instances. Suppressing
  `SingKind` instances provides an effective workaround for #150.
* Hook into the TH machinery's naming conventions for promoted and
  singled names. This fixes #204.

The vast majority of this patch simply adds plumbing by using
`OptionsMonad` in places that need it. See the `D.S.TH.Options`
module for where most of the new code is housed, as well as the
`T150` and `T204` test cases for examples of how to use it.
@RyanGlScott
Copy link
Collaborator Author

See #427.

RyanGlScott added a commit that referenced this issue Dec 22, 2019
This patch introduces an `Options` data type and an `mtl`-like
`OptionsMonad` class for monads that carry `Options`. At present,
the only things one can do with `Options` are:

* Toggle the generation of `SingKind` instances. Suppressing
  `SingKind` instances provides an effective workaround for #150.
* Hook into the TH machinery's naming conventions for promoted and
  singled names. This fixes #204.

The vast majority of this patch simply adds plumbing by using
`OptionsMonad` in places that need it. See the `D.S.TH.Options`
module for where most of the new code is housed, as well as the
`T150` and `T204` test cases for examples of how to use it.
RyanGlScott added a commit that referenced this issue Dec 23, 2019
This patch introduces an `Options` data type and an `mtl`-like
`OptionsMonad` class for monads that carry `Options`. At present,
the only things one can do with `Options` are:

* Toggle the generation of `SingKind` instances. Suppressing
  `SingKind` instances provides an effective workaround for #150.
* Hook into the TH machinery's naming conventions for promoted and
  singled names. This fixes #204.

The vast majority of this patch simply adds plumbing by using
`OptionsMonad` in places that need it. See the `D.S.TH.Options`
module for where most of the new code is housed, as well as the
`T150` and `T204` test cases for examples of how to use it.
RyanGlScott added a commit that referenced this issue Dec 24, 2019
This patch introduces an `Options` data type and an `mtl`-like
`OptionsMonad` class for monads that carry `Options`. At present,
the only things one can do with `Options` are:

* Toggle the generation of `SingKind` instances. Suppressing
  `SingKind` instances provides an effective workaround for #150.
* Hook into the TH machinery's naming conventions for promoted and
  singled names. This fixes #204.

The vast majority of this patch simply adds plumbing by using
`OptionsMonad` in places that need it. See the `D.S.TH.Options`
module for where most of the new code is housed, as well as the
`T150` and `T204` test cases for examples of how to use it.
RyanGlScott added a commit that referenced this issue Dec 26, 2019
This patch introduces an `Options` data type and an `mtl`-like
`OptionsMonad` class for monads that carry `Options`. At present,
the only things one can do with `Options` are:

* Toggle the generation of `SingKind` instances. Suppressing
  `SingKind` instances provides an effective workaround for #150.
* Hook into the TH machinery's naming conventions for promoted and
  singled names. This fixes #204.

The vast majority of this patch simply adds plumbing by using
`OptionsMonad` in places that need it. See the `D.S.TH.Options`
module for where most of the new code is housed, as well as the
`T150` and `T204` test cases for examples of how to use it.
RyanGlScott added a commit that referenced this issue Dec 27, 2019
* Bump major version to 2.7

* Introduce Options and OptionsMonad

This patch introduces an `Options` data type and an `mtl`-like
`OptionsMonad` class for monads that carry `Options`. At present,
the only things one can do with `Options` are:

* Toggle the generation of `SingKind` instances. Suppressing
  `SingKind` instances provides an effective workaround for #150.
* Hook into the TH machinery's naming conventions for promoted and
  singled names. This fixes #204.

The vast majority of this patch simply adds plumbing by using
`OptionsMonad` in places that need it. See the `D.S.TH.Options`
module for where most of the new code is housed, as well as the
`T150` and `T204` test cases for examples of how to use it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants