packages feed

evoke 0.2021.8.25 → 0.2021.9.14

raw patch · 9 files changed

+508/−86 lines, 9 filesdep −randomdep ~HUnitdep ~basedep ~ghc

Dependencies removed: random

Dependency ranges changed: HUnit, base, ghc, insert-ordered-containers, text

Files

+ README.markdown view
@@ -0,0 +1,404 @@+# Evoke++:crystal_ball: Evoke is a GHC plugin that automatically derives type class+instances without using generics or Template Haskell.++- [Motivation](#motivation)+- [Installation](#installation)+- [Usage](#usage)+- [Options](#options)+- [Type Classes](#type-classes)+  - [Arbitrary](#arbitrary)+  - [FromJSON](#fromjson)+  - [ToJSON](#tojson)+  - [ToSchema](#toschema)+- [Performance](#performance)+- [Benefits](#benefits)+- [Drawbacks](#drawbacks)++## Motivation++In Haskell there are many ways to provide type class instances for data types.+As a motivating example, let's say you have the following data type:++``` hs+data Person = Person+  { name :: String+  , age :: Int+  }+```++And let's also say that you want to send and receive this data type over the+wire as JSON. Typically in Haskell you would use the Aeson library and+implement the `FromJSON` and `ToJSON` instances. The simplest and easiest way+to do that is to write the instances by hand, like this:++``` hs+instance FromJSON Person where+  parseJSON = withObject "Person" $ \ object -> do+    theirName <- object .: "name"+    theirAge <- object .: "age"+    pure Person+      { name = theirName+      , age = theirAge+      }++instance ToJSON Person where+  toJSON person = object+    [ "name" .= name person+    , "age" .= age person+    ]+```++This is not difficult, but it's tedious and error prone. It's tedious because+the instance isn't doing anything interesting --- it's all boilerplate. (What's+worse: We haven't even written all the boilerplate! We should have implemented+`toEncoding` for better performance, but we didn't.) And it's error prone+because we have to type all the key names correctly, get them matched up to the+correct fields, and make sure that `FromJSON` and `ToJSON` agree with each+other.++Thankfully GHC has a couple ways to make this easier. The first is Template+Haskell (TH), and thankfully the Aeson library provides some TH functions for+generating instances. Instead of writing the above instances, we could simply+write this:++``` hs+$( deriveJSON defaultOptions ''Person )+```++The generated instance would be similar to what we wrote by hand, but we don't+have to actually write it. And if we update the data type, the instance will be+updated automatically to match.++Unfortunately this approach has one major problem: It forces the module to be+recompiled more often. If any of this module's transitive dependencies changes,+this module will be recompiled. For a small project that's not too bad, but for+a large project that means TH forces you to recompile way more often.++Fortunately there's another approach that supports generating the instances+without causing unnecessary recompilations: generics. It's also simple to use:++``` hs+data Person = Person+  { name :: String+  , age :: Int+  } deriving (Generic, FromJSON, ToJSON)+```++Again the generated instances will behavior similarly to our hand-written+instances, but we don't have to worry about keeping everything in sync. This+feels like the best of all worlds, so what's the problem?++Generics has one major problem: It's slow to compile. Unlike TH, it avoids+recompiling more often, but when it does recompile it's much slower. And just+like TH, this is fine for small projects but for large projects it means you'll+spend a lot of time waiting for generic type class instances to compile.++So what can you do? Ideally we could fix the recompilation checking for TH+and/or fix the compile time performance for generics. Doing either of those+things feels daunting to me, so I looked for alternative approaches. I saw that+it was technically possible for GHC plugins to generate instances, so I created+Evoke to explore that option.++## Installation++Currently Evoke only supports GHC 8.10. It is possible to support a wider range+of versions, but I haven't put in the legwork necessary to make that possible.+Please let me know if you'd like to use Evoke with a different version of GHC.++You can install Evoke by adding it to the `build-depends` section of your+`*.cabal` file. For example:++``` cabal+name: example+version: 0+library+  build-depends: evoke, whatever-else+--               ^^^^^+```++## Usage++Since Evoke is a GHC plugin, you must enable it before you can use it. To+enable it on a per-file basis, add the following pragma to the top of a Haskell+file:++``` hs+{-# OPTIONS_GHC -fplugin=Evoke #-}+--              ^^^^^^^^^^^^^^+```++It can be tedious to add that pragma to each file that needs it. Instead you+can enable Evoke for every file in a package by adding it to the `ghc-options`+section of your `*.cabal` file. For example:++``` cabal+name: example+version: 0+library+  ghc-options: -fplugin=Evoke -whatever-else+--             ^^^^^^^^^^^^^^+```++The performance impact of enabling Evoke for an entire package is negligible.+In practice I recommend enabling Evoke once in the `*.cabal` file rather than+with a pragma in each source file.++Enabling Evoke will not change the behavior of any modules that previously+compiled. In order to convince Evoke to derive an instance for you, you must+write a deriving clause like this:++``` hs+newtype Person = Person+  { name :: String+  } deriving SomeTypeClass via "Evoke"+--                         ^^^^^^^^^^^+```++Note that `"Evoke"` is a literal string. You do not need to have any language+extensions, such as `DerivingVia` or `DerivingStrategies`, enabled in order to+use Evoke.++## Options++The Evoke plugin itself accepts options, which you can pass using GHC's+`-fplugin-opt` option. For example, this will output the generated instances:++``` hs+{-# OPTIONS_GHC -fplugin=Evoke -fplugin-opt=Evoke:--verbose #-}+--                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^+```++Evoke will also output the instances that it generates when you pass the+`-ddump-deriv` flag to GHC. For example:++``` hs+{-# OPTIONS_GHC -fplugin=Evoke -ddump-deriv #-}+--                             ^^^^^^^^^^^^+```++Each type class can accept its own options, which you can pass by adding them+to the `"Evoke"` string used. For example:++``` hs+newtype Person = Person+  { name :: String+  } deriving SomeTypeClass via "Evoke --some-option"+--                                    ^^^^^^^^^^^^^+```++## Type Classes++These type classes accept the following options:++- `--kebab`: Convert field names into `kebab-case`. For example `firstName`+  would become `first-name`.++- `--camel`: Convert field names into `camelCase`. For example `FirstName`+  would become `firstName`. This is usually combined with `--strip PREFIX`.++- `--snake`: Convert field names into `snake_case`. For example `firstName`+  would become `first_name`.++- `--strip PREFIX`: Removes the given prefix from field names. If the field+  name doesn't begin with the prefix, an error will be thrown. For example,+  with `--strip person` the field name `personFirstName` would become+  `FirstName`.++- `--title`: Convert field names into `TitleCase`. For example `firstName`+  would become `FirstName`.++Options are processed in order. So for example `--strip person --kebab` would+change `personFirstName` into `first-name`.++### Arbitrary++Here's an example of how to derive `Arbitrary` using Evoke:++``` hs+{-# OPTIONS_GHC -fplugin=Evoke #-}+data Person = Person+  { name :: String+  , age :: Int+  } deriving Arbitrary via "Evoke"+```++And here's what the generated instance might look like:++``` hs+instance Test.QuickCheck.Arbitrary Person where+  arbitrary = do+    name_1 <- Test.QuickCheck.arbitrary+    age_2 <- Test.QuickCheck.arbitrary+    Control.Applicative.pure Person+      { name = name_1+      , age = age_2+      }+```++### FromJSON++Here's an example of how to derive `FromJSON` using Evoke:++``` hs+{-# OPTIONS_GHC -fplugin=Evoke #-}+data Person = Person+  { name :: String+  , age :: Int+  } deriving FromJSON via "Evoke"+```++And here's what the generated instance might look like:++``` hs+instance Data.Aeson.FromJSON Person where+  parseJSON = Data.Aeson.withObject "Main.Person" (\ object_1 -> do+    name_2 <- object_1 Data.Aeson..: Data.Text.pack "name"+    age_3 <- object_1 Data.Aeson..: Data.Text.pack "age"+    Control.Applicative.pure Person+      { name = name_2+      , age = age_3+      })+```++### ToJSON++Here's an example of how to derive `ToJSON` using Evoke:++``` hs+{-# OPTIONS_GHC -fplugin=Evoke #-}+data Person = Person+  { name :: String+  , age :: Int+  } deriving ToJSON via "Evoke"+```++And here's what the generated instance might look like:++``` hs+instance Data.Aeson.ToJSON Person where+  toJSON var_1 = Data.Aeson.object+    [ Data.Text.pack "name" Data.Aeson..= name var_1+    , Data.Text.pack "age" Data.Aeson..= age var_1+    ]+  toEncoding var_2 = Data.Aeson.pairs (Data.Monoid.mconcat+    [ Data.Text.pack "name" Data.Aeson..= name var_2+    , Data.Text.pack "age" Data.Aeson..= age var_2+    ])+```++### ToSchema++Here's an example of how to derive `ToSchema` using Evoke:++``` hs+{-# OPTIONS_GHC -fplugin=Evoke #-}+data Person = Person+  { name :: String+  , age :: Int+  } deriving ToSchema via "Evoke"+```++And here's what the generated instance might look like:++``` hs+instance Data.Swagger.ToSchema Person where+  declareNamedSchema _proxy_1 = do+    name_2 <- Data.Swagger.declareSchemaRef (Data.Proxy.Proxy :: Data.Proxy.Proxy String)+    age_3 <- Data.Swagger.declareSchemaRef (Data.Proxy.Proxy :: Data.Proxy.Proxy Int)+    Control.Applicative.pure (Data.Swagger.NamedSchema+      (Data.Maybe.Just (Data.Text.pack "Main.Person"))+      (Data.Monoid.mempty+          Control.Lens.& Data.Swagger.type_ Control.Lens.?~ Data.Swagger.SwaggerObject+          Control.Lens.& Data.Swagger.properties Control.Lens..~ Data.HashMap.Strict.InsOrd.fromList+            [ (Data.Text.pack "name", name_2)+            , (Data.Text.pack "age", age_3)+            ]+          Control.Lens.& Data.Swagger.required Control.Lens..~+            [ Data.Text.pack "name"+            , Data.Text.pack "age"+            ]))+```++Note that this instance will require the `ScopedTypeVariables` extension if the+type has any type variables that are used in any of the fields.++## Performance++There are several answers to the question: "How fast is Evoke?"++The first answer relates to the plugin itself. How long does it take the plugin+to run? If the plugin is enabled for a module that doesn't actually derive any+instances, it's basically instantaneous. In that case Evoke just has to walk+over the parsed module and make sure that nothing needs to be done. If the+module does derive some instances, it's still very fast. Generating the code+for the instances does not take an appreciable amount of time. I hesitate to+call it instantaneous, but you probably won't notice how long it takes.++The second answer relates to the code generated by the plugin. How long does it+take GHC to compile the generated code? Since this plugin generates code and+then hands it off to GHC, it's just as fast as manually writing the code+yourself. It turns out that TH has the same performance characteristics, except+that using TH causes more frequent recompiles. And compared to generic deriving+this plugin is much faster.++The last answer relates to the runtime performance of the code generated by the+plugin. How fast is the generated code? The answer here is practically the same+as the last one: Just as fast as if you wrote it manually. That's the same as+TH and quite a bit better than generics.++And now for some specific numbers. At [ACI Learning][] we have a Haskell+codebase with just over 400 data types that use Evoke for deriving at least one+type class instance. Previously we used generic deriving for those instances.+By switching from generics to Evoke, our compilation time improved by at least+20%! Here's a table summarizing the build times:++[ACI Learning]: https://www.acilearning.com++Flags | Generic | Evoke | Change | Percent+--- | --- | --- | --- | ---+`-O1 -j1` | 922s | 750s | -172s | 81%+`-O0 -j1`  | 372s | 229s | -143s | 62%+`-O0 -j8`  | 139s | 94s | -45s | 68%++Our runtime also improved slightly, along with lowered CPU and memory usage.+Those numbers are harder to quantify, but we definitely saw an improvement.++## Benefits++Compared to TH and generics, why might you prefer Evoke?++- It's fast to compile. Generating the instances is basically instantaneous.+  Compiling the instances takes just as long as if you wrote them manually.++- It doesn't affect recompilation checks. You can generate instances without+  forcing the module to be recompiled more frequently like you would with TH.++- It's easy to debug. The entire plugin operates as a source-to-source+  conversion process. You can output the generated source to remove the plugin+  entirely if you'd like. Or you can run it as a standalone executable.++- It doesn't require any language extensions. Obviously it requires a plugin+  instead.++## Drawbacks++Compared to TH and generics, why might you avoid Evoke?++- It operates syntactically. Things that are equivalent to the type checker,+  like `a` and `(a)`, typically confuse Evoke.++- It's tightly coupled to GHC. Since it operates on GHC's view of a parsed+  module, it naturally depends on that representation.++- It only supports a subset of possible data types. Currently it doesn't+  support types with multiple constructors or GADTs.++- Each type class must be supported explicitly. And this support must be+  provided by the plugin itself rather than by various other libraries like+  Aeson.++- Some things are uncomfortably magical. For instance a field with type+  `Maybe a` will be optional, but if its type is `Prelude.Maybe a` then it+  won't be optional.
evoke.cabal view
@@ -1,12 +1,13 @@ cabal-version: >= 1.10  name: evoke-version: 0.2021.8.25+version: 0.2021.9.14 synopsis: A GHC plugin to derive instances. description: Evoke is a GHC plugin to derive instances.  build-type: Simple category: Plugin+extra-source-files: README.markdown license-file: LICENSE.txt license: MIT maintainer: Taylor Fausak@@ -17,9 +18,8 @@  library   build-depends:-    base >= 4.14.1 && < 4.15-    , ghc >= 8.10.4 && < 8.11-    , random >= 1.2.0 && < 1.3+    base >= 4.14 && < 4.15+    , ghc >= 8.10.1 && < 8.11   default-language: Haskell2010   exposed-modules:     Evoke@@ -55,12 +55,12 @@     base     , aeson     , evoke-    , HUnit >= 1.6.2 && < 1.7-    , insert-ordered-containers >= 0.2.5 && < 0.3+    , HUnit >= 1.6.1 && < 1.7+    , insert-ordered-containers >= 0.2.3 && < 0.3     , lens >= 4.19.2 && < 4.20     , QuickCheck >= 2.14.2 && < 2.15     , swagger2 >= 2.6 && < 2.7-    , text >= 1.2.4 && < 1.3+    , text >= 1.2.3 && < 1.3   default-language: Haskell2010   ghc-options: -rtsopts -threaded   hs-source-dirs: src/test
src/lib/Evoke.hs view
@@ -349,7 +349,8 @@         generate moduleName lIdP lHsQTyVars lConDecls options srcSpan       Nothing -> Hsc.throwError srcSpan $ Ghc.text "unsupported type class" -    Monad.when (Config.verbose config) $ do+    verbose <- isVerbose config+    Monad.when verbose $ do       dynFlags <- Ghc.getDynFlags       IO.liftIO $ do         putStrLn $ replicate 80 '-'@@ -357,6 +358,11 @@         mapM_ (putStrLn . Ghc.showSDocDump dynFlags . Ghc.ppr) lHsDecls      pure (lImportDecls, lHsDecls)++isVerbose :: Config.Config -> Ghc.Hsc Bool+isVerbose config = do+  dynFlags <- Ghc.getDynFlags+  pure $ Config.verbose config || Ghc.dopt Ghc.Opt_D_dump_deriv dynFlags  getGenerator :: Ghc.LHsSigType Ghc.GhcPs -> Maybe Common.Generator getGenerator lHsSigType = do
src/lib/Evoke/Constant/Module.hs view
@@ -1,46 +1,44 @@-{- hlint ignore "Use camelCase" -}- module Evoke.Constant.Module-  ( control_applicative-  , control_lens-  , data_aeson-  , data_hashMap_strict_insOrd-  , data_maybe-  , data_monoid-  , data_proxy-  , data_swagger-  , data_text-  , test_quickCheck+  ( controlApplicative+  , controlLens+  , dataAeson+  , dataHashMapStrictInsOrd+  , dataMaybe+  , dataMonoid+  , dataProxy+  , dataSwagger+  , dataText+  , testQuickCheck   ) where  import qualified Module as Ghc -control_applicative :: Ghc.ModuleName-control_applicative = Ghc.mkModuleName "Control.Applicative"+controlApplicative :: Ghc.ModuleName+controlApplicative = Ghc.mkModuleName "Control.Applicative" -control_lens :: Ghc.ModuleName-control_lens = Ghc.mkModuleName "Control.Lens"+controlLens :: Ghc.ModuleName+controlLens = Ghc.mkModuleName "Control.Lens" -data_aeson :: Ghc.ModuleName-data_aeson = Ghc.mkModuleName "Data.Aeson"+dataAeson :: Ghc.ModuleName+dataAeson = Ghc.mkModuleName "Data.Aeson" -data_hashMap_strict_insOrd :: Ghc.ModuleName-data_hashMap_strict_insOrd = Ghc.mkModuleName "Data.HashMap.Strict.InsOrd"+dataHashMapStrictInsOrd :: Ghc.ModuleName+dataHashMapStrictInsOrd = Ghc.mkModuleName "Data.HashMap.Strict.InsOrd" -data_maybe :: Ghc.ModuleName-data_maybe = Ghc.mkModuleName "Data.Maybe"+dataMaybe :: Ghc.ModuleName+dataMaybe = Ghc.mkModuleName "Data.Maybe" -data_monoid :: Ghc.ModuleName-data_monoid = Ghc.mkModuleName "Data.Monoid"+dataMonoid :: Ghc.ModuleName+dataMonoid = Ghc.mkModuleName "Data.Monoid" -data_proxy :: Ghc.ModuleName-data_proxy = Ghc.mkModuleName "Data.Proxy"+dataProxy :: Ghc.ModuleName+dataProxy = Ghc.mkModuleName "Data.Proxy" -data_swagger :: Ghc.ModuleName-data_swagger = Ghc.mkModuleName "Data.Swagger"+dataSwagger :: Ghc.ModuleName+dataSwagger = Ghc.mkModuleName "Data.Swagger" -data_text :: Ghc.ModuleName-data_text = Ghc.mkModuleName "Data.Text"+dataText :: Ghc.ModuleName+dataText = Ghc.mkModuleName "Data.Text" -test_quickCheck :: Ghc.ModuleName-test_quickCheck = Ghc.mkModuleName "Test.QuickCheck"+testQuickCheck :: Ghc.ModuleName+testQuickCheck = Ghc.mkModuleName "Test.QuickCheck"
src/lib/Evoke/Generator/Arbitrary.hs view
@@ -2,6 +2,7 @@   ( generate   ) where +import qualified Data.List as List import qualified Evoke.Constant.Module as Module import qualified Evoke.Generator.Common as Common import qualified Evoke.Hs as Hs@@ -19,16 +20,18 @@     [x] -> pure x     _ -> Hsc.throwError srcSpan $ Ghc.text "requires exactly one constructor"   fields <--    mapM (fromField srcSpan) . concatMap Constructor.fields $ Type.constructors-      type_+    mapM (fromField srcSpan)+    . List.sortOn Field.name+    . concatMap Constructor.fields+    $ Type.constructors type_ -  applicative <- Common.makeRandomModule Module.control_applicative-  quickCheck <- Common.makeRandomModule Module.test_quickCheck+  applicative <- Common.makeRandomModule Module.controlApplicative+  quickCheck <- Common.makeRandomModule Module.testQuickCheck   let     lImportDecls = Hs.importDecls       srcSpan-      [ (Module.control_applicative, applicative)-      , (Module.test_quickCheck, quickCheck)+      [ (Module.controlApplicative, applicative)+      , (Module.testQuickCheck, quickCheck)       ]      bindStmts = fmap
src/lib/Evoke/Generator/Common.hs view
@@ -9,10 +9,11 @@   ) where  import qualified Bag as Ghc+import qualified Control.Monad.IO.Class as IO import qualified Data.Char as Char+import qualified Data.IORef as IORef import qualified Data.List as List import qualified Data.Maybe as Maybe-import qualified Data.Word as Word import qualified Evoke.Hs as Hs import qualified Evoke.Hsc as Hsc import qualified Evoke.Type.Constructor as Constructor@@ -21,7 +22,7 @@ import qualified GHC.Hs as Ghc import qualified GhcPlugins as Ghc import qualified System.Console.GetOpt as Console-import qualified System.Random as Random+import qualified System.IO.Unsafe as Unsafe import qualified Text.Printf as Printf  type Generator@@ -198,27 +199,24 @@ -- | Makes a random variable name using the given prefix. makeRandomVariable :: Ghc.SrcSpan -> String -> Ghc.Hsc (Ghc.LIdP Ghc.GhcPs) makeRandomVariable srcSpan prefix = do-  word16 <- randomWord16+  n <- bumpCounter   pure . Ghc.L srcSpan . Ghc.Unqual . Ghc.mkVarOcc $ Printf.printf-    "%s%04x"+    "%s%d"     prefix-    word16--randomWord16 :: Ghc.Hsc Word.Word16-randomWord16 = Random.randomIO+    n  -- | Makes a random module name. This will convert any periods to underscores -- and add a unique suffix. -- -- >>> makeRandomModule "Data.Aeson"--- "Data_Aeson_01ef"+-- "Data_Aeson_1" makeRandomModule :: Ghc.ModuleName -> Ghc.Hsc Ghc.ModuleName makeRandomModule moduleName = do-  word16 <- randomWord16+  n <- bumpCounter   pure . Ghc.mkModuleName $ Printf.printf-    "%s_%04x"+    "%s_%d"     (underscoreAll moduleName)-    word16+    n  underscoreAll :: Ghc.ModuleName -> String underscoreAll = fmap underscoreOne . Ghc.moduleNameString@@ -302,3 +300,10 @@   Ghc.GRHSs Ghc.noExtField [Hs.grhs srcSpan hsExpr]     . Ghc.L srcSpan     $ Ghc.EmptyLocalBinds Ghc.noExtField++bumpCounter :: IO.MonadIO m => m Word+bumpCounter = IO.liftIO . IORef.atomicModifyIORef' counterRef $ \ n -> (n + 1, n)++counterRef :: IORef.IORef Word+counterRef = Unsafe.unsafePerformIO $ IORef.newIORef 0+{-# NOINLINE counterRef #-}
src/lib/Evoke/Generator/FromJSON.hs view
@@ -2,6 +2,7 @@   ( generate   ) where +import qualified Data.List as List import qualified Evoke.Constant.Module as Module import qualified Evoke.Generator.Common as Common import qualified Evoke.Hs as Hs@@ -25,19 +26,20 @@    fields <-     mapM (fromField srcSpan modifyFieldName)+    . List.sortOn Field.name     . concatMap Constructor.fields     $ Type.constructors type_ -  applicative <- Common.makeRandomModule Module.control_applicative-  aeson <- Common.makeRandomModule Module.data_aeson-  text <- Common.makeRandomModule Module.data_text+  applicative <- Common.makeRandomModule Module.controlApplicative+  aeson <- Common.makeRandomModule Module.dataAeson+  text <- Common.makeRandomModule Module.dataText   object <- Common.makeRandomVariable srcSpan "object_"   let     lImportDecls = Hs.importDecls       srcSpan-      [ (Module.control_applicative, applicative)-      , (Module.data_aeson, aeson)-      , (Module.data_text, text)+      [ (Module.controlApplicative, applicative)+      , (Module.dataAeson, aeson)+      , (Module.dataText, text)       ]      bindStmts = fmap
src/lib/Evoke/Generator/ToJSON.hs view
@@ -2,6 +2,7 @@   ( generate   ) where +import qualified Data.List as List import qualified Evoke.Constant.Module as Module import qualified Evoke.Generator.Common as Common import qualified Evoke.Hs as Hs@@ -25,21 +26,22 @@    fieldNames <-     mapM (fromField modifyFieldName)+    . List.sort     . fmap Field.name     . concatMap Constructor.fields     $ Type.constructors type_ -  aeson <- Common.makeRandomModule Module.data_aeson-  monoid <- Common.makeRandomModule Module.data_monoid-  text <- Common.makeRandomModule Module.data_text+  aeson <- Common.makeRandomModule Module.dataAeson+  monoid <- Common.makeRandomModule Module.dataMonoid+  text <- Common.makeRandomModule Module.dataText   var1 <- Common.makeRandomVariable srcSpan "var_"   var2 <- Common.makeRandomVariable srcSpan "var_"   let     lImportDecls = Hs.importDecls       srcSpan-      [ (Module.data_aeson, aeson)-      , (Module.data_monoid, monoid)-      , (Module.data_text, text)+      [ (Module.dataAeson, aeson)+      , (Module.dataMonoid, monoid)+      , (Module.dataText, text)       ]      toPair lRdrName (occName, fieldName) =
src/lib/Evoke/Generator/ToSchema.hs view
@@ -2,6 +2,7 @@   ( generate   ) where +import qualified Data.List as List import qualified Evoke.Constant.Module as Module import qualified Evoke.Generator.Common as Common import qualified Evoke.Hs as Hs@@ -26,29 +27,30 @@    fields <-     mapM (fromField srcSpan modifyFieldName)+    . List.sortOn Field.name     . concatMap Constructor.fields     $ Type.constructors type_ -  applicative <- Common.makeRandomModule Module.control_applicative-  lens <- Common.makeRandomModule Module.control_lens-  hashMap <- Common.makeRandomModule Module.data_hashMap_strict_insOrd-  dataMaybe <- Common.makeRandomModule Module.data_maybe-  monoid <- Common.makeRandomModule Module.data_monoid-  proxy <- Common.makeRandomModule Module.data_proxy-  swagger <- Common.makeRandomModule Module.data_swagger-  text <- Common.makeRandomModule Module.data_text+  applicative <- Common.makeRandomModule Module.controlApplicative+  lens <- Common.makeRandomModule Module.controlLens+  hashMap <- Common.makeRandomModule Module.dataHashMapStrictInsOrd+  dataMaybe <- Common.makeRandomModule Module.dataMaybe+  monoid <- Common.makeRandomModule Module.dataMonoid+  proxy <- Common.makeRandomModule Module.dataProxy+  swagger <- Common.makeRandomModule Module.dataSwagger+  text <- Common.makeRandomModule Module.dataText   ignored <- Common.makeRandomVariable srcSpan "_proxy_"   let     lImportDecls = Hs.importDecls       srcSpan-      [ (Module.control_applicative, applicative)-      , (Module.control_lens, lens)-      , (Module.data_hashMap_strict_insOrd, hashMap)-      , (Module.data_maybe, dataMaybe)-      , (Module.data_monoid, monoid)-      , (Module.data_proxy, proxy)-      , (Module.data_swagger, swagger)-      , (Module.data_text, text)+      [ (Module.controlApplicative, applicative)+      , (Module.controlLens, lens)+      , (Module.dataHashMapStrictInsOrd, hashMap)+      , (Module.dataMaybe, dataMaybe)+      , (Module.dataMonoid, monoid)+      , (Module.dataProxy, proxy)+      , (Module.dataSwagger, swagger)+      , (Module.dataText, text)       ]      toBind field var =