some-dict-of (empty) → 0.1.0.0
raw patch · 7 files changed
+306/−0 lines, 7 filesdep +basedep +constraintsdep +some-dict-ofsetup-changed
Dependencies added: base, constraints, some-dict-of
Files
- LICENSE +30/−0
- README.md +71/−0
- Setup.hs +2/−0
- changelog.md +3/−0
- some-dict-of.cabal +52/−0
- src/SomeDictOf.hs +132/−0
- test/Spec.hs +16/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2021 Matthew Parsons++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 Felipe Lessa 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,71 @@+# `some-dict-of`++Have you ever needed an existential wrapper that just guaranteed class+membership, but you didn't need to know the specifics of the type in question?++Well, you probably *don't* - it's not idiomatic Haskell for the most part. But+sometimes this comes up, and I wanted a nice packaging of the technique. I wrote+this mostly to support the `discover-instances` library.++## `SomeDictOf`++Let's consider a type that wraps anything that can be shown, along with a value+of that type. We can make one by hand like this:++```haskell+data SomeShowable where+ SomeShowable :: forall a. Show a => a -> SomeShowable++showList :: [SomeShowable] -> [String]+showList = map (\(SomeShowable a) -> show a)+```++With `SomeDictOf`, we can generalize this pattern to other classes and even+other containers.++```haskell+someShowable :: SomeDictOf Identity Show+someShowable =+ SomeDictOf (Identity (3 :: Int))+```++We're carrying an `Int`, but the type does not reveal this. We can create a list+of values like this, and we can call `show` on them.++```haskell+showValues+ :: [SomeDictOf Identity Show]+ -> [String]+showValues =+ map (\(SomeDictOf (Identity showable)) -> show showable)+```++We can also carry around evidence of a type class instance, and then write+generic stuff based on it. Consider the+[`PersistEntity`](https://hackage.haskell.org/package/persistent-2.13.1.1/docs/Database-Persist-Class-PersistEntity.html#t:PersistEntity)+from the `persistent` database library.++```haskell+tables :: [SomeDictOf Proxy PersistEntity]+tables = [ SomeDictOf (Proxy @User), SomeDictOf (Proxy @Organization) ]+```++Now we can iterate over these types and, say, load all the rows out of the+database and verify they parse.++```haskell+checkRows :: SqlPersistT [[PersistValue]]+checkRows = do+ forM tables $ \(SomeDictOf (Proxy :: Proxy table)) -> do+ results <- selectList [] [] :: SqlPersistT m [Entity table]+ pure (map toPersistValue results)+```++Or we can do some metaprogramming based on their `EntityDef`s, since+`PersistEntity` has a method `Proxy a -> EntityDef`.++```haskell+getDefinitions :: [EntityDef]+getDefinitions =+ map (\(SomeDictOf p) -> entityDef p) tables+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ changelog.md view
@@ -0,0 +1,3 @@+# 0.1.0.0++- Initial Release
+ some-dict-of.cabal view
@@ -0,0 +1,52 @@+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: some-dict-of+version: 0.1.0.0+synopsis: Carry evidence of constraints around+description: Please see the README on GitHub at <https://github.com/parsonsmatt/some-dict-of#readme>+category: Constraints+homepage: https://github.com/parsonsmatt/some-dict-of#readme+bug-reports: https://github.com/parsonsmatt/some-dict-of/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/some-dict-of++library+ exposed-modules:+ SomeDictOf+ other-modules:+ Paths_some_dict_of+ hs-source-dirs:+ src+ build-depends:+ base >=4.7 && <5+ , constraints+ default-language: Haskell2010++test-suite discover-instances-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_some_dict_of+ hs-source-dirs:+ test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , constraints+ , some-dict-of+ default-language: Haskell2010
+ src/SomeDictOf.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module SomeDictOf+ ( module SomeDictOf+ -- * Re-exports+ , module Data.Functor.Identity+ , module Data.Proxy+ , module Data.Constraint+ ) where++import Data.Constraint+import Data.Functor.Identity+import Data.Kind+import Data.Proxy++-- | A datatype that carries evidence of type class membership for some+-- type, along with a datatype indexed by that type. This is confusing, so+-- let's look at some examples.+--+-- The alias @'SomeDict' clazz@ uses 'Proxy' for the datatype index. This means+-- that the wrapper is a @'Proxy' :: 'Proxy' a@, and you know that the type+-- @a@ has an instance of @clazz a@. We can construct a value+--+-- @+-- showSomeDict :: SomeDictOf Identity Show+-- showSomeDict =+-- SomeDict (Identity 3 :: Identity Int)+-- @+--+-- A value of @'SomeDict' 'Show'@ contains 'Proxy' value. When you pattern+-- match on the 'SomeDict', you know that the given @'Proxy' :: 'Proxy' a@+-- has an instance of 'Show'.+--+-- For the most part, this isn't useful,+--+-- @since 0.1.0.0+data SomeDictOf (f :: k -> Type) (c :: k -> Constraint) where+ SomeDictOf :: c a => f a -> SomeDictOf f c++-- | In the case where you only need evidence that the dictionary exists,+-- then you can use this type. By carrying a 'Proxy' instead of a real+-- value, we can summon these up whenever we have an instance of the type in+-- question.+--+-- @since 0.1.0.0+type SomeDict = SomeDictOf Proxy++-- | Construct a 'SomeDict' with the type in question. Use with+-- @TypeApplications@.+--+-- Example:+--+-- @+-- showDict :: SomeDict Show+-- showDict = someDict @Int+--+-- entityDict :: SomeDict PersistEntity+-- entityDict = someDict @User+-- @+--+-- @since 0.1.0.0+someDict :: forall a c. c a => SomeDict c+someDict = SomeDictOf (Proxy :: Proxy a)++-- | Construct a 'SomeDict' based on a 'Dict' from the "Data.Constraint" module.+--+-- @since 0.1.0.0+fromDict :: forall a c. Dict (c a) -> SomeDict c+fromDict Dict = SomeDictOf (Proxy @a)++-- | Unpack a 'SomeDictOf' and attain access to the evidence that the+-- underlying type satisfies the instance in question.+--+-- @since 0.1.0.0+withSomeDictOf+ :: SomeDictOf f c+ -> (forall a. c a => f a -> r)+ -> r+withSomeDictOf (SomeDictOf p) k =+ k p++-- | Transform the type index used in the 'SomeDictOf'. The provided function is+-- unable to inspect the type, but you will have the class operations available+-- to it.+--+-- Example:+--+-- @+-- provideMempty ;: SomeDictOf Proxy Monoid -> SomeDictOf Identity Monoid+-- provideMempty = mapSomeDictOf (\proxy -> Identity mempty)+-- @+--+-- @since 0.1.0.0+mapSomeDictOf+ :: (forall a. c a => f a -> g a)+ -> SomeDictOf f c+ -> SomeDictOf g c+mapSomeDictOf f (SomeDictOf fa) =+ SomeDictOf (f fa)++-- | Not really sure what this might be useful for.+--+-- Examples:+--+-- @+-- shows :: SomeDictOf [] Show+-- shows = SomeDictOf [True, False, True]+--+-- forgotten :: [SomeDictOf Proxy Show]+-- forgotten = forgetContents show+--+-- main = do+-- forM forgotten $ \(SomeDictOf Proxy) -> do+-- print 10+-- @+--+-- The above program should output 10 three times.+--+-- @since 0.1.0.0+forgetContents+ :: forall f c. Functor f+ => SomeDictOf f c+ -> f (SomeDictOf Proxy c)+forgetContents (SomeDictOf (xs :: f a)) =+ fmap (\(_ :: a) -> SomeDictOf (Proxy @a)) xs
+ test/Spec.hs view
@@ -0,0 +1,16 @@+module Main where++import SomeDictOf++main :: IO ()+main = putStrLn "it compiles at least"++-- note: if this changes, update the README+showValues+ :: [SomeDictOf Identity Show]+ -> [String]+showValues =+ map (\(SomeDictOf (Identity showable)) -> show showable)++slamMempty :: SomeDictOf Proxy Monoid -> SomeDictOf Identity Monoid+slamMempty = mapSomeDictOf (\pa -> Identity mempty)