-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | A safe interface for Const summarization -- -- A safe interface for using the Const functor to do -- summarization. See the Constable module for details. @package constable @version 0.1.0.0 -- | This module provides an interface for the 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 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" getConst in your codebase by way of an HLint rule:
--
--
-- - functions:
-- - {name: getConst, within: []}
--
--
module Constable
-- | Unwraps Const. Throws a type error if that Const has a
-- function type for its second argument.
getConstSafe :: FullyAppliedConst b => Const a b -> a
-- | Smart constructor for 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 m a
-- | Smart constructor for Const like mkConst but where the
-- argument is the result type applied to some type constructor
-- f.
mkConstF :: f a -> (f a -> m) -> Const m a
-- | The Const functor.
data Const a (b :: k)
-- | A pattern synonym for Const that upholds the fully applied
-- constructor invariant for its second type argument.
pattern Const :: FullyAppliedConst b => a -> Const a b
-- | A constraint enforcing that getConstSafe is only used with
-- non-partially applied constructors.
type family FullyAppliedConst (a :: Type) :: Constraint