packages feed

constable (empty) → 0.1.0.0

raw patch · 5 files changed

+198/−0 lines, 5 filesdep +basedep +constabledep +doctest

Dependencies added: base, constable, doctest

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for constable++## 0.1.0.0 -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2022, Aaron Allen++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 Aaron Allen 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.
+ constable.cabal view
@@ -0,0 +1,41 @@+cabal-version:      3.0+name:               constable+version:            0.1.0.0+synopsis: A safe interface for Const summarization+description: A safe interface for using the @Const@ functor to do summarization. See the @Constable@ module for details.+license:            BSD-3-Clause+license-file:       LICENSE+author:             Aaron Allen+maintainer:         aaronallen8455@gmail.com+bug-reports:        https://github.com/aaronallen8455/constable/issues+-- copyright:+category:           Development+build-type:         Simple+extra-doc-files:    CHANGELOG.md+-- extra-source-files:++common warnings+    ghc-options: -Wall+                 -Wno-unticked-promoted-constructors++library+    import:           warnings+    exposed-modules:  Constable+    -- other-modules:+    -- other-extensions:+    build-depends:    base <= 5+    hs-source-dirs:   src+    default-language: Haskell2010++test-suite constable-test+    import:           warnings+    default-language: Haskell2010+    -- other-modules:+    -- other-extensions:+    type:             exitcode-stdio-1.0+    hs-source-dirs:   test+    main-is:          Main.hs+    build-depends:+        base,+        constable,+        doctest
+ src/Constable.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-| This module provides an interface for the 'Const.Const' functor which facilitates+   using it safely for summarization via the applicative instance @Monoid m => Applicative (Const m)@.++   As an example, say we have a record of numbers and a function that sums up+   the fields of that record. We want to leverage the type system to ensure+   that if the record changes (field are added or removed), then the summing+   function will not compile until it is updated to match.++   As a first attempt, we might write something like this:++   @+   import Data.Functor.Const+   import Data.Monoid++   data Parts = MkParts+     { part1 :: Int+     , part2 :: Int+     , part3 :: Int+     }++   sumParts :: Parts -> Int+   sumParts parts = getSum . getConst '$'+     MkParts+       '<$>' Const (Sum $ part1 parts)+       '<*>' Const (Sum $ part2 parts)+       '<*>' Const (Sum $ part3 parts)+   @++   At first glance it looks like this accomplishes our goal, but there is a+   serious bug. If we remove a field from the record then @sumParts@ will+   indeed fail to compile, but if we add a field then it will compile without+   issue! This is because we didn't explicitly provide a type to the @Const@+   expression being constructed and so the compiler infers it to be a function+   type rather than the fully applied type that we expect. This means we must+   always remember to add explicit type signatures or type applications+   anywhere we use this pattern.++   If we instead use the 'getConstSafe' version of 'Const.getConst' exported by this+   module then we don't need to worry about this issue because the compiler+   will complain if the inferred type of a @Const@ is for a function type.++   If you use HLint, you can go one step further and ban the use of the+   "unsafe" 'Const.getConst' in your codebase by way of an HLint rule:++   @+   - functions:+     - {name: getConst, within: []}+   @+-}+module Constable+  ( -- * API+    getConstSafe+  , mkConst+  , mkConstF+  , Const.Const(Constable.Const)+  -- * Internal+  , FullyAppliedConst+  ) where++import           Data.Coerce (coerce)+import qualified Data.Functor.Const as Const+import           Data.Kind (Type, Constraint)+import           GHC.TypeLits (TypeError, ErrorMessage(..))++-- | A constraint enforcing that 'getConstSafe' is only used with non-partially+-- applied constructors.+type family FullyAppliedConst (a :: Type) :: Constraint where+  FullyAppliedConst (a -> b) =+    TypeError (Text "Const used with partially applied constructor: '"+          :<>: ShowType (a -> b)+          :<>: Text "'")+  FullyAppliedConst a = ()++-- | Unwraps 'Const.Const'. Throws a type error if that 'Const.Const' has a+-- function type for its second argument.+getConstSafe :: FullyAppliedConst b => Const.Const a b -> a+getConstSafe = coerce++-- | Smart constructor for 'Const.Const' that enforces that the value it contains is+-- built from a value of the result type. This provides an extra measure of type safety.+--+-- @+-- data Summable = MkSummable+--   { s1 :: Int+--   , s2 :: Double+--   , s3 :: Integer+--   } deriving (Show)+--+-- test :: Summable+-- test = Summable+--   { s1 = 1+--   , s2 = 2+--   , s3 = 3+--   }+--+-- 'getConstSafe' '$' MkSummable+--   '<$>' mkConst (s1 test) (Sum . fromIntegral)+--   '<*>' mkConst (s2 test) Sum+--   '<*>' mkConst (s3 test) (Sum . fromIntegral)+-- @+mkConst :: a -> (a -> m) -> Const.Const m a+mkConst a toM = coerce (toM a)++-- | Smart constructor for 'Const.Const' like 'mkConst' but where the argument is the+-- result type applied to some type constructor @f@.+mkConstF :: f a -> (f a -> m) -> Const.Const m a+mkConstF fa toM = coerce (toM fa)++-- | A pattern synonym for 'Const.Const' that upholds the fully applied+-- constructor invariant for its second type argument.+pattern Const :: FullyAppliedConst b => a -> Const.Const a b+pattern Const a = Const.Const a+{-# COMPLETE Const #-}
+ test/Main.hs view
@@ -0,0 +1,4 @@+import           Test.DocTest++main :: IO ()+main = doctest [ "-isrc" , "test/Test.hs" ]