1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Language.Fiddle.Internal.UnitNumbers where
import Data.Aeson (FromJSON (..), ToJSON (..))
import Text.Printf
-- | 'NumberWithUnit' represents a numeric value associated with a unit type.
-- The unit type 'u' is a phantom type, meaning it carries no runtime data
-- but provides compile-time type safety for the units.
-- The actual value is of type 'i', such as 'Int' or 'Integer'.
newtype NumberWithUnit u i = NumberWithUnit i
deriving newtype (Real, Enum, Num, Eq, Ord, Integral, PrintfArg)
-- | Custom 'Show' instance for 'NumberWithUnit' to display the numeric value.
instance (Show i) => Show (NumberWithUnit u i) where
show (NumberWithUnit b) = show b
-- | JSON serialization for 'NumberWithUnit', converting to and from JSON using
-- the underlying numeric type.
instance (ToJSON i) => ToJSON (NumberWithUnit u i) where
toJSON (NumberWithUnit b) = toJSON b
instance (FromJSON i) => FromJSON (NumberWithUnit u i) where
parseJSON v = NumberWithUnit <$> parseJSON v
-- | Type alias for 'NumberWithUnit' where the underlying numeric type is 'Int'.
-- This is a shorthand for convenience when working with integers.
type N u = NumberWithUnit u Int
-- | Phantom types representing different units that can be used with
-- 'NumberWithUnit'. These types carry no data but serve as compile-time
-- markers for the units.
data Bits
data Bytes
data Address
data Unitless
-- | 'NamedUnit' is a typeclass for units that can be represented as strings.
-- It provides a 'unitName' method for displaying a formatted representation
-- of the unit.
class NamedUnit u where
unitName :: N u -> String
-- | 'NamedUnit' instance for 'Bits'. It displays the value followed by "bit"
-- or "bits", depending on whether the value is 1 or not.
instance NamedUnit Bits where
unitName 1 = "1 bit"
unitName n = show n ++ " bits"
-- | 'NamedUnit' instance for 'Bytes'. It displays the value followed by "byte"
-- or "bytes", depending on whether the value is 1 or not.
instance NamedUnit Bytes where
unitName 1 = "1 byte"
unitName n = show n ++ " bytes"
-- | Convert a value from 'Bits' to 'Bytes'. This returns a tuple containing the
-- number of complete bytes and the remaining bits.
bitsToBytes :: N Bits -> (N Bytes, N Bits)
bitsToBytes (NumberWithUnit a) =
let (y, i) = divMod a 8 in (NumberWithUnit y, NumberWithUnit i)
-- | Convert a value from 'Bytes' to 'Bits'. This multiplies the byte value by
-- 8 to get the corresponding number of bits.
bytesToBits :: N Bytes -> N Bits
bytesToBits (NumberWithUnit a) = NumberWithUnit (a * 8)
-- | Multiplication operator for 'NumberWithUnit' values. The result retains
-- the original unit 'u', while the right operand is of the 'Unitless' type.
-- This operator is useful for scaling values by a unitless factor.
(.*.) :: N u -> N Unitless -> N u
(.*.) (NumberWithUnit a) (NumberWithUnit b) = NumberWithUnit (a * b)
|