Is there a way to shorten this deriving clause?
Asked Answered
D

1

8

Is there a way to write the following:

 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE DeriveAnyClass     #-}

 data X = A | B | C
     deriving (Eq, Ord, Show, Read, Data, SymWord, HasKind, SMTValue)

So that the deriving clause can be shortened somehow, to something like the following:

 data X = A | B | C deriving MyOwnClass

I'd like to avoid TH if at all possible, and I'm happy to create a new class that has all those derived classes as its super-class as necessary (as in MyOwnClass above), but that doesn't really work with the deriving mechanism. With constraint kinds extension, I found that you can write this:

type MyOwnClass a = (Eq a, Ord a, Show a, Read a, Data a, SymWord a, HasKind a, SMTValue a)

Unfortunately, I cannot put that in the deriving clause. Is there some magic to make this happen?

EDIT From the comments, it appears TH might be the only viable choice here. (The CPP macro is really not OK!) If that's the case, the sketch of a TH solution will be nice to see.

Darkish answered 15/7, 2017 at 0:14 Comment(2)
deriving clauses are in fact magic. Without TH, I'm afraid there's not really a way to do what you want to do.Szabo
Consider making a RFC in github.com/haskell/rfcsOrnstead
S
8

There's bad and easy way to do it and good but hard way. As Silvio Mayolo said you can use TemplateHaskell to write such function. This way is hard and rather complex way. The easier way is to use C-preprocessor like this:

{-# LANGUAGE CPP #-}

#define MY_OWN_CLASS (Eq, Ord, Show, Read, Data, SymWord, HasKind, SMTValue)

data X = A | B | C
     deriving MY_OWN_CLASS

UPDATE (17.07.2016): ideas & sketch of TH solution

Before introducing sketch of solution I will illustrate why this is harder to do with TH. deriving-clause is not some independent clause, it's a part of data declaration so you can't encode only part inside deriving unfortunately. The general approach of writing any TH code is to use runQ command on brackets to see what you should write in the end. Like this:

ghci> :set -XTemplateHaskell
ghci> :set -XQuasiQuotes 
ghci> import Language.Haskell.TH
ghci> runQ [d|data A = B deriving (Eq, Show)|]
[ DataD
    []
    A_0
    []
    Nothing
    [ NormalC B_1 [] ]
    [ ConT GHC.Classes.Eq , ConT GHC.Show.Show ]
]

Now you see that type classes for deriving are specified as last argument of DataD — data declaration — constructor. The workaround for your problem is to use -XStadandaloneDeriving extension. It's like deriving but much powerful though also much verbose. Again, to see, what exactly you want to generate, just use runQ:

ghci> data D = T
ghci> :set -XStandaloneDeriving 
ghci> runQ [d| deriving instance Show D |]
[ StandaloneDerivD [] (AppT (ConT GHC.Show.Show) (ConT Ghci5.D)) ]

You can use StandaloneDerivD and other constructors directly or just use [d|...|]-brackets though they have more magic but they give you list of Dec (declarations). If you want to generate several declarations then you should write you function like this:

{-# LANGUAGE TemplateHaskell    #-}
{-# LANGUAGE QuasiQuotes        #-}
{-# LANGUAGE StandaloneDeriving #-}

module Deriving where

import Language.Haskell.TH

boilerplateAnnigilator :: Name -> Q [Dec]
boilerplateAnnigilator typeName = do
    let typeCon = conT typeName
    [d|deriving instance Show $(typeCon)
       deriving instance Eq   $(typeCon)
       deriving instance Ord  $(typeCon)
      |]

Brief tutorial can be found here.

And then you can use it in another file (this is TH limitation called staged restriction: you should define macro in one file but you can't use it in the same file) like this:

{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell    #-}

import Deriving

data X = A | B | C

boilerplateAnnigilator ''X

You should put other type classes you want inside boilerplateAnnigilator function. But this approach only works for non-parametrized class. If you have data MyData a = ... then standalone deriving should look like:

deriving instance Eq a => MyData a

And if you want your TH macro work for parametrized classes as well, then you basically should implement whole logic of GHC compiler by deducing whether type have type variables or not and generate instances depending on that. But this is much harder. I think that the best solution is to just make ticket in GHC compiler and let authors implement such feature called deriving aliases :)

Secondbest answered 15/7, 2017 at 14:52 Comment(3)
Can you sketch the TH solution?Darkish
@LeventErkok I wrote little sketch of TH solution. Now you can see why it's less trivial :)Secondbest
Thanks for the most helpful write-up. This worked like a charm for my use case! Much appreciated.Darkish

© 2022 - 2024 — McMap. All rights reserved.