Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to prevent Data.Generics.Alloy.GenInstances from scanning Data.Text.Internal?

Tags:

haskell

I need to do transformations on an AST; here's a portion of the AST:

data Expr
  = BinExpr { beOp    :: BinaryOp
            , beLeft  :: Expr
            , beRight :: Expr }
  | Name Text
  | IntegerLit Integer
  | StringLit Text
  deriving (Data, Typeable)

And this is a fairly complex AST, so there are many types involved.

I'm using alloy to generate the generic transformations, specifically:

autoGen :: IO ()
autoGen = do
  createDirectoryIfMissing True baseDir
  writeInstancesTo inst doc imports targetFile
  where
    inst = allInstances GenWithoutOverlapped
    doc = [genInstance (undefined :: Doc)]
    imports = header ++ instanceImports

Now, this was fine when using String, but I'm trying to migrate to Data.Text. When the code generation runs, it's reading into the internals of Data.Text like so:

instance (Alloy ([(GHC.Types.Char)]) (f :- ops) BaseOp) =>
     Alloy ((Data.Text.Internal.Text)) BaseOp (f :- ops) where
  transform _ ops (Data.Text.Internal.pack a0)
      =  Data.Text.Internal.pack
     (transform ops BaseOp (a0))

I believe pack is tied to GHC internals so that's not a valid pattern match, and regardless, having the code mucking with the internals of a Data.Text is liable to break the invariants. (Edit: it looks like there's an instance Data Text where gfoldl f z txt = z packf(unpack txt) declaration, but regardless, I don't need/want to traverse Text values.)

Is there a way to force Alloy to treat a type as atomic? I'm hoping to avoid a newtype to wrap Text as all the code working with ASTs would need to deal with it, which rather defeats the purpose of using generics to avoid boilerplate.

like image 696
Ben Avatar asked Oct 28 '22 10:10

Ben


1 Answers

Maybe try this trick: we parameterize the Expr type to override the Data instance used for Text when deriving instances with alloy.

data Expr_ text
  = BinExpr { beOp    :: BinaryOp
            , beLeft  :: Expr_ text
            , beRight :: Expr_ text }
  | Name text
  ...
  | StringLit text

The rest of the code base can use this synonym, hopefully without breaking too much with type inference issues.

type Expr = Expr_ Text

But for Data-generic operations, we use a newtype wrapper around Text and make it behave like a nullary constructor, hoping alloy doesn't need the result of gunfold (or perhaps you could make it behave like a string using pattern synonyms).

newtype DataText = DataText Text

instance Data DataText where
  gunfold _ f _ = f undefined
  ...

autoGen will then specialize everything at DummyText.

Use Data.Coerce.coerce to easily convert between functions on Expr_ DataText and Expr.

coerce :: Expr_ DataText -> Expr
coerce :: Expr -> Expr_ DataText
coerce :: (Expr_ DataText -> Expr_ DataText) -> Expr -> Expr

This might be used to write instances of alloy's type classes for Expr, based on the instances that were derived from you. It's a bit of boilerplate, but hopefully it can be contained and hidden without affecting the rest of the code.

like image 99
Li-yao Xia Avatar answered Nov 09 '22 17:11

Li-yao Xia