packages feed

discover-instances (empty) → 0.1.0.0

raw patch · 7 files changed

+370/−0 lines, 7 filesdep +basedep +discover-instancesdep +some-dict-ofsetup-changed

Dependencies added: base, discover-instances, some-dict-of, template-haskell, th-compat

Files

+ ChangeLog.md view
@@ -0,0 +1,7 @@+# Changelog for discover-instances++## Unreleased changes++## 0.1.0.0++- Initial Release
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Matt Parsons (c) 2021++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Matt Parsons nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,3 @@+# discover-instances++A Haskell package that defines a `TemplateHaskell` helper for discovering and working with instances of a type class.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ discover-instances.cabal view
@@ -0,0 +1,55 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.4.+--+-- see: https://github.com/sol/hpack++name:           discover-instances+version:        0.1.0.0+description:    Please see the README on GitHub at <https://github.com/parsonsmatt/discover-instances#readme>+homepage:       https://github.com/parsonsmatt/discover-instances#readme+bug-reports:    https://github.com/parsonsmatt/discover-instances/issues+author:         Matt Parsons+maintainer:     parsonsmatt@gmail.com+copyright:      Matt Parsons+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://github.com/parsonsmatt/discover-instances++library+  exposed-modules:+      DiscoverInstances+  other-modules:+      Paths_discover_instances+  hs-source-dirs:+      src+  ghc-options: -Wall+  build-depends:+      base >=4.12 && <5+    , some-dict-of+    , template-haskell+    , th-compat+  default-language: Haskell2010++test-suite discover-instances-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Paths_discover_instances+  hs-source-dirs:+      test+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.12 && <5+    , discover-instances+    , some-dict-of+    , template-haskell+    , th-compat+  default-language: Haskell2010
+ src/DiscoverInstances.hs view
@@ -0,0 +1,248 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE PartialTypeSignatures #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}++-- | Generally speaking, this module is only useful to discover instances+-- of unary type classes where the instance is unconstrained.+--+-- That is to say - not all that useful in libraries.+--+-- However, this has ended up being super useful to me in a bunch of+-- application contexts. Consider @persistent-discover@, which grabs all+-- the @PersistEntity@ instances in scope and makes the @[EntityDef]@ for+-- them. Or consider a front-end types generating module, which needs to+-- import a ton of modules, and then call @toSomeFrontEndClass :: SomeClass+-- a => Proxy a -> SomeThing@ on each thing.+--+-- This library can simplify that process.+--+-- @since 0.1.0.0+module DiscoverInstances+    (+    -- * The main interface+      discoverInstances+    -- * Using the results of 'discoverInstances'+    -- $using+    , withInstances+    , forInstances+    , module SomeDictOf+    -- * Re-exports+    , module Data.Proxy+    ) where++import Data.Proxy+import Data.Typeable+import Language.Haskell.TH hiding (cxt)+import Language.Haskell.TH.Syntax+import Language.Haskell.TH.Syntax.Compat+import SomeDictOf++-- | This TemplateHaskell function accepts a type and splices in a list of+-- 'SomeDict's that provide evidence that the type is an instance of+-- the class that you asked for.+--+-- There are some limitations.+--+-- * The class can only accept a single parameter.+-- * The instances returned do not have a context.+--+-- Example Use:+--+-- @+-- eq :: ['SomeDict' Eq]+-- eq = $$(discoverInstances)+-- @+--+-- This function uses typed @TemplateHaskell@, which means you don't need to+-- provide a type annotation directly. However, you can pass a type directly.+--+-- @+-- ord :: ['SomeDict' 'Ord']+-- ord = $$(discoverInstances @Ord)+-- @+--+-- GHC supports using the @$$@ syntax without parentheses if the expression is+-- a single term. So you can also write this:+--+-- @+-- functor :: ['SomeDict' 'Functor']+-- functor = $$discoverInstances+-- @+--+-- But you'll get an error if you type-apply like that.+--+-- @since 0.1.0.0+discoverInstances :: forall (c :: _ -> Constraint) . (Typeable c) => SpliceQ [SomeDict c]+discoverInstances = liftSplice $ do+    let+        className =+            show (typeRep (Proxy @c))+    instanceDecs <- reifyInstances (mkName className) [VarT (mkName "a")]++    dicts <- fmap listTE $ traverse decToDict instanceDecs++    examineSplice [|| concat $$(liftSplice $ pure dicts) ||]++-- $using+--+-- Once you've acquired a @'SomeDict' c@ for some type class @c@ that you care+-- about, it's not entirely clear how you might use it.+--+-- The "SomeDictOf" module contains functions for working with these, but the+-- simplest thing to do is probably pattern match on them directly. The use case+-- that this library was designed to support is iterating over all the visible+-- instances of a class and performing some operation on the class.+--+-- Consider the @persistent@ database library. The @PersistEntity@ type class+-- defines a method @entityDef :: PersistEntity a => proxy a -> EntityDef@,+-- where the @EntityDef@ type contains information that relates the type to the+-- database encoding for the type. Let's get all the @EntityDef@s for the types+-- in scope:+--+-- @+-- entityDefs :: [EntityDef]+-- entityDefs =+--      'map'+--          (\\('SomeDictOf' proxy) -> entityDef proxy)+--          $$('discoverInstances' \@PersistEntity)+-- @+--+-- The @EntityDef@ for an entity include the documentation comments written for+-- the entity. So we can print out all the documentation for the database+-- tables.+--+-- Another intended use is to render generated code. We use the+-- @aeson-typescript@ library to generate TypeScript code for our API. Without+-- this library, we maintain a list of types to generate code for. This list is+-- duplicated in the imports, as well as the data declarations.+--+-- @+-- import Model.Foo+-- import Model.Bar+-- import Model.Baz+-- import Model.Quux+--+-- renderTypeScript :: IO ()+-- renderTypeScript = do+--     writeToFile $ concat+--         [ writeType \@Foo+--         , writeType \@Bar+--         , writeType \@Baz+--         , writeType \@Quux+--         ]+-- @+--+-- With 'discoverInstances', we can skip a lot of this work.+--+-- @+-- import Model.Foo+-- import Model.Bar+-- import Model.Baz+-- import Model.Quux+--+-- renderTypeScript :: IO ()+-- renderTypeScript = do+--     writeToFile $ concat+--         $ flip map $$('discoverInstances' \@TypeScript)+--         $ \\('SomeDictOf' ('Proxy' :: 'Proxy' ty) ->+--             writeType \@ty+-- @+--+-- The above two patterns are encapsulated in 'withInstances'. We can rewrite+-- them like this:+--+-- @+-- entityDefs =+--     'withInstances'+--         $$(discoverInstances \@PersistEntity)+--         $ \\proxy -> entityDef proxy+--+-- renderTypeScript :: IO ()+-- renderTypeScript = do+--     writeToFile $ concat+--         $ 'withInstances' $$('discoverInstances' \@TypeScript)+--         $ \\('Proxy' :: 'Proxy' ty) ->+--             writeType \@ty+-- @+--+-- Another use case is to load all models out of the database, to ensure that+-- serialization and deserialization logic works. 'forInstances' is useful for+-- operating over instances effectfully.+--+-- @+--loadAllModels :: SqlPersistM ()+--loadAllModels = do+--    'forInstances' $$('discoverInstances' \@PersistEntity) \\('Proxy' :: 'Proxy' a) -> do+--        selectList [] []  :: SqlPersistM [Entity a]+-- @++-- | An alias for the pattern:+--+-- @+-- flip map $$discoverInstances $ \\('SomeDictOf' p) -> f p+-- @+--+-- @since 0.1.0.0+withInstances+    :: Functor f+    => f (SomeDict c) -> (forall a. c a => Proxy a -> r) -> f r+withInstances dicts f =+    fmap (\(SomeDictOf p) -> f p) dicts++-- | An alias for the pattern:+--+-- @+-- for $$discoverInstances $ \\('SomeDictOf' p) -> do+--     f p+-- @+--+-- @since 0.1.0.0+forInstances+    :: (Traversable t, Applicative f)+    => t (SomeDict c)+    -> (forall a. c a => Proxy a -> f r)+    -> f (t r)+forInstances dicts f =+    traverse (\(SomeDictOf p) -> f p) dicts++listTE :: [TExp a] -> TExp [a]+listTE = TExp . ListE . map unType++decToDict :: forall k (c :: k -> Constraint). InstanceDec -> Q (TExp [SomeDict c])+decToDict = \case+    InstanceD _moverlap cxt typ _decs ->+        case cxt of+            [] -> do+                let+                    t =+                        case typ of+                            AppT _ t' ->+                               stripSig t'+                            _ ->+                                t+                    stripSig (SigT a _) =+                        a+                    stripSig x =+                        x+                    proxy =+                        [| Proxy :: Proxy $(pure t) |]+                unsafeTExpCoerce [| [ SomeDictOf $proxy ] |]+            _ -> do+                -- reportWarning $+                --     "I haven't figured out how to put constrained instances on here, so I'm skipping the type: "+                --     <> show typ+                --     <> ", context: "+                --     <> show cxt+                examineSplice [|| [] ||]++    _ -> do+        reportWarning $+            "discoverInstances called on 'reifyInstances' somehow returned something that wasn't a type class instance."+        examineSplice [|| [] ||]
+ test/Spec.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}++{-# OPTIONS_GHC -ddump-splices #-}++import Data.Foldable+import Data.Proxy+import Data.Typeable+import DiscoverInstances++show :: [SomeDict Show]+show = $$(discoverInstances @Show)++eq :: [SomeDict Eq]+eq = $$(discoverInstances)++functor :: [SomeDict Functor]+functor = $$discoverInstances++main :: IO ()+main = do+    for_ functor $ \(SomeDictOf p@(Proxy :: Proxy t)) -> do+        print 1234