convert-1.0.1: src/Data/Convert/Bound.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ViewPatterns #-}
module Data.Convert.Bound where
import Data.Convert.Base
import Data.Typeable
import Control.Applicative
import Language.Haskell.TH hiding (Type, Safety, Safe, Unsafe)
import Data.Monoid
import GHC.TypeLits
import Prelude hiding (Bounded, maxBound, minBound)
import qualified Prelude as Prelude
-- === The runtime used safe conversion ===
-- TODO [wd]: We can optimize it further. Right now we are converting everything to Rational and comparing.
-- It's because sometimes we are converting between types of different bounds like Char and Int8
-- - neither can we safely convert Char to Int 8 nor vice versa.
-- We can though make typeclasses that will find the smallest super-type for a given type pair
-- and convert both values to that super-type
boundedConversion :: (Bounded b, Ord a, Convertible a Rational, Convertible (Bounds b) (Bounds Rational))
=> (a -> b) -> (a -> Either BoundError b)
boundedConversion (func :: a -> b) inp = if (convert inp) `boundedBy` (convert (bounds :: Bounds b) :: Bounds Rational)
then Right $ func inp
else Left BoundError
-- === Errors ===
data BoundError = BoundError deriving (Show)
-- === Conversion ===
data Conversion = Conversion (Q Exp) Type Type
class Conversions a b where
conversions :: Q Exp -> a -> b -> [Conversion]
-- utils
genConversion :: Conversion -> Q Dec
genConversion c@(Conversion qexp (Type name bounds) (Type name' bounds')) = do
exp <- qexp :: Q Exp
let convf name fmod = [ValD (VarP $ mkName name) (NormalB $ fmod exp) []]
return $ if bounds `isSubBound` bounds'
then InstanceD [] (AppT (AppT (ConT $ mkName "Convertible") (ConT name)) (ConT name')) $ convf "convert" id
else InstanceD [] (AppT (AppT (AppT (ConT $ mkName "MaybeConvertible") (ConT name)) (ConT $ mkName "BoundError")) (ConT name')) $ convf "tryConvert" $ AppE (VarE $ mkName "boundedConversion")
genConversions :: [Conversion] -> Q [Dec]
genConversions = mapM genConversion
-- instances
instance Conversions [Type] [Type] where conversions f a b = Conversion f <$> a <*> b
instance Conversions Type [Type] where conversions f a b = Conversion f a <$> b
instance Conversions [Type] Type where conversions f a b = flip (Conversion f) b <$> a
instance Conversions Type Type where conversions f a b = [Conversion f a b]
instance Show Conversion where
show (Conversion _ a b) = "Conversion " -- <> " " <> name a <> " " <> name b
-- === Type ===
data Type = Type { name :: Name
, tbounds :: (Bounds Integer)
} deriving (Show, Eq)
-- === Layout ===
data Layout = IntLayout Sign Integer
| InfiniteLayout
deriving (Show, Eq)
data Sign = Signed
| Unsigned
deriving (Show, Eq)
layoutBounds :: Layout -> Bounds Integer
layoutBounds = \case
InfiniteLayout -> infiniteBounds
IntLayout s i -> Bounds (Value (- base)) (Value $ base - 1)
where base = case s of
Signed -> 2 ^ (i - 1)
Unsigned -> 2 ^ i
-- === Value ===
data Value a = MinusInfinity
| Value a
| Infinity
deriving (Show, Functor, Eq)
-- instances
instance Num a => Num (Value a) where
fromInteger = Value . fromInteger
instance Convertible a b => Convertible (Value a) (Value b) where
convert (Value a) = Value $ convert a
instance Ord a => Ord (Value a) where
compare MinusInfinity MinusInfinity = EQ
compare MinusInfinity a = LT
compare a MinusInfinity = GT
compare Infinity Infinity = EQ
compare Infinity a = GT
compare a Infinity = LT
compare (Value a) (Value a') = compare a a'
-- === Bounds ===
data Bounds a = Bounds (Value a) (Value a) deriving (Show, Functor, Eq)
class Bounded a where
bounds :: Bounds a
-- utils
infiniteBounds :: Bounds a
infiniteBounds = Bounds MinusInfinity Infinity
isSubBound :: Ord a => Bounds a -> Bounds a -> Bool
isSubBound (Bounds min max) (Bounds min' max') = min >= min' && max <= max'
boundedBy :: Ord a => a -> Bounds a -> Bool
boundedBy (Value -> a) (Bounds min max) = a >= min && a <= max
-- instances
instance {-# OVERLAPPABLE #-} Convertible a b => Convertible (Bounds a) (Bounds b) where
convert (Bounds a b) = Bounds (convert a) (convert b)
instance {-# OVERLAPPABLE #-} (Prelude.Bounded a)
=> Bounded a where
bounds = Bounds (Value Prelude.minBound) (Value Prelude.maxBound)
instance {-# OVERLAPPABLE #-} Bounded Float where bounds = infiniteBounds
instance {-# OVERLAPPABLE #-} Bounded Double where bounds = infiniteBounds
instance {-# OVERLAPPABLE #-} Bounded Rational where bounds = infiniteBounds
instance {-# OVERLAPPABLE #-} Bounded Integer where bounds = infiniteBounds