packages feed

validated-literals 0.1.0 → 0.2.0

raw patch · 3 files changed

+163/−15 lines, 3 files

Files

ValidLiterals.hs view
@@ -1,36 +1,93 @@-{-# LANGUAGE DefaultSignatures #-}+-------------------------------------------------------------------------------+-- |+-- Module      :  ValidLiterals+-- Copyright   :  (C) 2015 Merijn Verstraaten+-- License     :  BSD-style (see the file LICENSE)+-- Maintainer  :  Merijn Verstraaten <merijn@inconsistent.nl>+-- Stability   :  experimental+-- Portability :  portable+--+-- To disallow invalid input it is common to define (new)types with hidden+-- data constructors. Forcing the user to go through a smart-constructor that+-- enforces invariants and returns @Maybe ResultType@, preventing the+-- construction of data with invalid values.+--+-- However, it is __also__ common to want to include literal values of such+-- types in source text. Things of textual literals for HTML, HTTP, etc.+-- In such cases smart-constructors force us to handle potential conversion+-- failures at runtime, or abusing functions like @fromJust@ to break away all+-- the safety smart-constructors provide. All this despite the fact that we+-- can statically know at compile time that the conversion will always succeed+-- or always fails.+--+-- This package provides a typeclasses for using TH to validate the+-- correctness of provided literals at compile. This lets you define, e.g.,+-- @newtype Even = Even Integer@ and write:+--+-- @+-- x :: Even+-- x = $$(valid 38)+-- @+--+-- This will check, at compile time, that the provided 'Integer' is, in fact,+-- even and unwrap it from 'Maybe', avoiding the runtime check.+------------------------------------------------------------------------------- {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TemplateHaskell #-}-module ValidLiterals where+module ValidLiterals+    ( Validate (fromLiteral)+    , valid+    , validInteger+    , validRational+    , validString+    , validList+    ) where -import Data.Maybe-import Language.Haskell.TH import Language.Haskell.TH.Syntax -class Validate a b where-    fromLiteral :: a -> Maybe b--    spliceValid :: a -> b -> Q (TExp b)-    default spliceValid :: Lift b => a -> b -> Q (TExp b)-    spliceValid _ v = [|| v ||]+import ValidLiterals.Class +-- | The core function of ValidLiterals, use this together with Typed Template+-- Haskell splices to insert validated literals into your code. For example, if+-- we assume @newtype ASCII = ASCII Char@ where @ASCII@ should only contain+-- ASCII characters, we would write:+--+-- @+-- {-\# LANGUAGE TemplateHaskell #-}+--+-- import ValidLiterals+--+-- x :: ASCII+-- x = $$(valid 'c')+-- @ valid :: Validate a b => a -> Q (TExp b) valid input = case fromLiteral input of     Nothing -> fail "Invalid input used for type-safe validated literal!"     Just result -> spliceValid input result +-- | Integer literals lead to obnoxious defaulting complaints by GHC, by+-- using this function you can force the defaulting to 'Integer', silencing+-- these warnings.+--+-- Since 'Integral' literals use @fromInteger :: Num a => Integer -> a@ this+-- function cannot cost you any precision. validInteger :: Validate Integer b => Integer -> Q (TExp b) validInteger = valid +-- | Same as 'validInteger', but for 'Fractional' values.+--+-- Since 'Fractional' literals use+-- @fromRational :: Fractional a => Rational -> a@ this function cannot cost+-- you any precision. validRational :: Validate Rational b => Rational -> Q (TExp b) validRational = valid +-- | Same as 'validInteger', but for when enabling @OverloadedStrings@ makes+-- 'String' literals polymorphic. validString :: Validate String b => String -> Q (TExp b) validString = valid +-- | Same as 'validInteger', but for when enabling @OverloadedLists@ makes list+-- literals polymorphic. validList :: Validate [a] b => [a] -> Q (TExp b) validList = valid--hackySpliceValid :: (Lift a, Validate a b) => a -> b -> Q (TExp b)-hackySpliceValid v _ = [|| fromJust (fromLiteral v) ||]
+ ValidLiterals/Class.hs view
@@ -0,0 +1,66 @@+-------------------------------------------------------------------------------+-- |+-- Module      :  ValidLiterals+-- Copyright   :  (C) 2015 Merijn Verstraaten+-- License     :  BSD-style (see the file LICENSE)+-- Maintainer  :  Merijn Verstraaten <merijn@inconsistent.nl>+-- Stability   :  experimental+-- Portability :  portable+--+-- __WARNING: For implementors of new 'Validate' instances only!__+--+-- This module exposes functions whose use is easily screwed up. Users of+-- validated literals should use the interface exported by "ValidLiterals".+-- This module only exists for implementers of new 'Validate' instances.+-------------------------------------------------------------------------------+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TemplateHaskell #-}+module ValidLiterals.Class where++import Data.Maybe+import Language.Haskell.TH+import Language.Haskell.TH.Syntax++-- | Class for validated, compile-time, partial conversions from type 'a' to+-- 'b'.+class Validate a b where+    -- | Converts 'a' values into validated 'b' values+    fromLiteral :: a -> Maybe b++    -- | __WARNING: Don't use! For implementors of 'Validate' instances only!__+    --+    -- Produces the actual Typed Template Haskell splice for the validated+    -- value to splice into the source text. Since you may want to implement a+    -- 'Validate' instance for types that cannot have 'Lift' instances, this+    -- function receives __both__ the original 'a' value and the resulting 'b'+    -- as input.+    --+    -- This allows it to either:+    --+    --      1. Splice the resulting 'b' into the source using it's Lift+    --      instance, or a custom splice.+    --      2. Splice the initial 'a' into the source using it's Lift instance,+    --      and perform the conversion again at runtime, and coercing via,+    --      e.g., 'fromJust'. Since 'fromLiteral' is pure (right?!) this should+    --      be perfectly safe.+    --+    -- Clearly, splicing the result directly is much safer (it avoids any+    -- shenanigans with 'unsafePerformIO') and more efficient (no conversion at+    -- runtime). However, this is just not always possible, since, for example,+    -- 'ByteString' does not have a 'Lift' instance and we may still want to+    -- have validated newtypes of 'ByteString'.+    --+    -- __Default implementation:__ The default implementation (using+    -- DefaultSignatures) uses 'b''s 'Lift' instance to do the efficient thing+    -- of directly splicing the resulting 'b' into the source.+    spliceValid :: a -> b -> Q (TExp b)+    default spliceValid :: Lift b => a -> b -> Q (TExp b)+    spliceValid _ v = [|| v ||]++-- | Default implementation for 'spliceValid', uses the 'Lift' instance for 'a'+-- and 'fromJust' to redo the conversion at runtime and (unsafely) coerce from+-- 'Maybe b' to 'b'. . Since 'fromLiteral' is pure (right?!) this should be+-- perfectly safe.+hackySpliceValid :: (Lift a, Validate a b) => a -> b -> Q (TExp b)+hackySpliceValid v _ = [|| fromJust (fromLiteral v) ||]
validated-literals.cabal view
@@ -1,5 +1,5 @@ Name:                validated-literals-Version:             0.1.0+Version:             0.2.0  Homepage:            https://github.com/merijn/validated-literals Bug-Reports:         https://github.com/merijn/validated-literals/issues@@ -19,6 +19,30 @@ Synopsis:            Compile-time checking for partial smart-constructors  Description:+    To disallow invalid input it is common to define (new)types with hidden+    data constructors. Forcing the user to go through a smart-constructor that+    enforces invariants and returns @Maybe ResultType@, preventing the+    construction of data with invalid values.+    .+    However, it is __also__ common to want to include literal values of such+    types in source text. Things of textual literals for HTML, HTTP, etc.+    In such cases smart-constructors force us to handle potential conversion+    failures at runtime, or abusing functions like @fromJust@ to break away all+    the safety smart-constructors provide. All this despite the fact that we+    can statically know at compile time that the conversion will always succeed+    or always fails.+    .+    This package provides a typeclasses for using TH to validate the+    correctness of provided literals at compile. This lets you define, e.g.,+    @newtype Even = Even Integer@ and write:+    .+    @+    x :: Even+    x = $$(valid 38)+    @+    .+    This will check, at compile time, that the provided @Integer@ is, in fact,+    even and unwrap it from @Maybe@, avoiding the runtime check.  Extra-Source-Files:     examples/ByteString.hs@@ -34,6 +58,7 @@                         MultiParamTypeClasses, TemplateHaskell    Exposed-Modules:      ValidLiterals+                        ValidLiterals.Class   Other-Modules:            Build-Depends:        base >=4.8 && <4.9