Skip to content

Instantly share code, notes, and snippets.

@pxqr
Created March 29, 2014 17:50
Show Gist options
  • Save pxqr/9858955 to your computer and use it in GitHub Desktop.
Save pxqr/9858955 to your computer and use it in GitHub Desktop.
parsing and pretty printing of some quantities with units by introducing newtype wrappers. Despite of pretty printing, Read/Show instances obey standart rules. This might be useful in some CLI programs and options parsing.
-- SI binary prefixes of positive power.
--
-- > http://physics.nist.gov/cuu/Units/binary.html
--
{-# LANGUAGE Safe #-}
module Data.Unit.Bin
( Unit(..)
, prefix, fromPrefix, multiplier
, unit, formatS
) where
import Control.Applicative
import Data.Maybe
import Data.Word
import qualified Data.Unit.Dec as U10
data Unit
= Kibi -- ^ 2 ^ 10
| Mebi -- ^ 2 ^ 20
| Gibi -- ^ 2 ^ 30
| Tebi -- ^ 2 ^ 40
| Pebi -- ^ 2 ^ 50
| Exbi -- ^ 2 ^ 60
| Zebi -- ^ 2 ^ 70
| Yobi -- ^ 2 ^ 80
deriving (Show, Read, Eq, Ord, Bounded, Enum)
convert :: (Enum a, Enum b) => a -> b
convert = toEnum . fromEnum
prefix :: Unit -> String
prefix u = (: "i") $ fromJust $ lookup (convert u) U10.table
fromPrefix :: String -> Maybe Unit
fromPrefix (c : 'i' : []) = convert <$> U10.fromPrefix c
fromPrefix _ = Nothing
multiplier :: Num a => Unit -> a
multiplier u = 2 ^ (10 * (succ (fromEnum u)))
unit :: (Ord a, Num a) => a -> Maybe Unit
unit x | x < 2 ^ (10 :: Word) = Nothing
| x < 2 ^ (20 :: Word) = Just Kibi
| x < 2 ^ (30 :: Word) = Just Mebi
| x < 2 ^ (40 :: Word) = Just Gibi
| x < 2 ^ (50 :: Word) = Just Tebi
| x < 2 ^ (60 :: Word) = Just Pebi
| x < 2 ^ (70 :: Word) = Just Exbi
| x < 2 ^ (80 :: Word) = Just Zebi
| otherwise = Just Yobi
formatS :: (Ord a, Num a, Show a, Integral a) => a -> ShowS
formatS x = maybe (shows x) pp (unit x)
where
pp u = shows (x `div` multiplier u) . showString (prefix u)
-- SI decimal prefixes of positive power.
--
-- > http://www.bipm.org/en/si/prefixes.html
--
{-# LANGUAGE Safe #-}
module Data.Unit.Dec
( Unit
, table, fromPrefix, prefix
, multiplier, unit, formatS
)
where
import Data.Maybe
import Data.Word
import Data.Tuple
data Unit
= Kilo -- ^ 10 ^ 3
| Mega -- ^ 10 ^ 6
| Giga -- ^ 10 ^ 9
| Tera -- ^ 10 ^ 12
| Peta -- ^ 10 ^ 15
| Exa -- ^ 10 ^ 18
| Zetta -- ^ 10 ^ 21
| Yotta -- ^ 10 ^ 24
deriving (Show, Read, Eq, Ord, Bounded, Enum)
table :: [(Unit, Char)]
table = map mk [minBound .. maxBound]
where
mk u = (u, head (show u))
fromPrefix :: Char -> Maybe Unit
fromPrefix c = lookup c (map swap table)
-- | Uppercase.
prefix :: Unit -> Char
prefix u = fromJust (lookup u table)
multiplier :: Num a => Unit -> a
multiplier u = 10 ^ (3 * fromEnum u + 3)
unit :: (Ord a, Num a) => a -> Maybe Unit
unit x | x < 10 ^ (3 :: Word) = Nothing
| x < 10 ^ (6 :: Word) = Just Kilo
| x < 10 ^ (9 :: Word) = Just Mega
| x < 10 ^ (12 :: Word) = Just Giga
| x < 10 ^ (15 :: Word) = Just Tera
| x < 10 ^ (18 :: Word) = Just Peta
| x < 10 ^ (21 :: Word) = Just Exa
| x < 10 ^ (24 :: Word) = Just Zetta
| otherwise = Just Yotta
formatS :: (Ord a, Num a, Show a, Integral a) => a -> ShowS
formatS x = maybe (shows x) pp (unit x)
where
pp u = shows (x `div` multiplier u) . showChar (prefix u)
-- This module provides types which can be used for reading or
-- writing values of some specific quantity.
--
-- For example this program:
--
-- > main = do Size i <- read <$> System.IO.getLine
-- > print (Size i)
--
-- will read /size/ of something in (not nessary pretty) format
-- and output the same value in pretty form:
--
-- > "1B" -> "1B"
-- > "20000" -> "20KB"
-- > "10M" -> "10MB"
-- > "100gib" -> "107GiB"
--
-- You could also use the /i/ as ordinary value of Word64. Parsing
-- is case insensitive.
--
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.Unit
( Count(Count, getCount)
, Size (Size, getSize)
) where
import Control.Applicative
import Data.Char
import Data.Unit.Bin as U2
import Data.Unit.Dec as U10
-- | Should be used for count of something. Suffixed with multiplier
-- without any unit.
--
newtype Count a = Count { getCount :: a }
deriving (Eq, Ord, Bounded, Enum, Real, Num, Integral)
instance (Show a, Integral a) => Show (Count a) where
showsPrec _ = U10.formatS . getCount
instance (Read a, Num a) => Read (Count a) where
readsPrec p s = mk <$> readsPrec p s
where
mk (i, a : b : xs)
| Just u <- U2.fromPrefix [toUpper a, toLower b]
= (Count (i * (U2.multiplier u)), xs)
mk (i, x : xs) | Just u <- U10.fromPrefix (toUpper x)
= (Count (i * (U10.multiplier u)), xs)
mk (i, xs) = (Count i, xs)
-- | Should be used for size of data in bytes. Suffixed with unit 'B'
-- and multiplier by decimal power.
--
-- Its guarantieed that @(read . show) :: Count -> Size@ will work for
-- any 'Count', but the opposite isn't true.
--
newtype Size a = Size { getSize :: a }
deriving (Eq, Ord, Bounded, Enum, Real, Num, Integral)
instance (Show a, Integral a) => Show (Size a) where
showsPrec _ s = U10.formatS (getSize s) . showChar 'B'
instance (Read a, Num a) => Read (Size a) where
readsPrec p s = mk <$> readsPrec p s
where
mk (Count i, x : xs) | toUpper x == 'B'
= (Size i, xs)
mk (Count i, xs) = (Size i, xs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment