{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreidbmcehgfsx5wuglm6h6m7guf6ie6twu66vk2px4m2dhx3dd4oskq",
    "uri": "at://did:plc:pi6woz4d47bkuws673w2il2r/app.bsky.feed.post/3mgg5nisycwr2"
  },
  "path": "/t/sneak-peek-bolt-math/13766#post_1",
  "publishedAt": "2026-03-06T19:29:05.000Z",
  "site": "https://discourse.haskell.org",
  "tags": [
    "deconstruction of the `Bits` class",
    "my postings"
  ],
  "textContent": "# Fixing the Num hierarchy for Great Good\n\n\n\nThe Haskell numerical hierarchy is now dust, scattered to the winds.\n\nIt should come as no surprise to anyone following my work, that if I am complaining about something, it is probably because I am working on fixing it. There is very little point to moaning about something if you aren’t willing to do something about it. One of the things that I have complained about most, is the `Num` hierarchy, and so *snap*.\n\nThe deconstruction of the `Bits` class such that it butts up against the bottom of the `Num` hierarchy just begs for unification, and the game engine that I am slowly but inevitably building with all of these things (math, memory, cryptography) requires a level mathematical precision that the `base` does not allow. If you’ve seen my postings about not just `Boolean` but `Logic` and `Lattice`, well, this is where that has been leading.\n\nHere goes: I am giving Haskell the math library that it deserves.\n\n## The problem\n\nIt has always bothered me that Haskell, supposedly one of the best programming languages, has what is hands-down almost objectively one of the worst built-in math libraries.\n\nTake, for example, that:\n\n  * syntactic operators are defined as part of critical typeclass definitions\n  * Literal support and conversion requires full arithmetic implementations\n  * we have _three_ different power operators - `^`, `^^`, and `**`\n  * the definition of Num / Fractional fundamentally disallow instances for vectors and polynomials and etc\n  * It is impossible to support things like symbolic math\n\n\n\nThis causes heaps of problems, ranging from subtle things like having partial `Num` implementations just to access `fromInteger`, to more obviously destructive effects, such as the explosion of operators in libraries like `linear`. Implementing things is a pain in the arse - have you memorized the hierarchy, can you tell me what class `fromRational` is defined in?\n\n* * *\n\nThere are two major distinct issues that cause most of these problems, and they can be solved with the same resolution - they are the primary causes of the issues in the `Num` hierarchy, with most other effects being secondary to one of them:\n\n  * Literal syntactic support being tied to numeric / ring behavior\n  * The implicit assumption of homogenous operations in a field (hah) that is often heterogenous\n\n\n\nThe former is an issue because I shouldn’t have to implement `(+), (-), (*), negate, abs, signum` _just_ to get access to `fromInteger` for a type that is representable as an integer but fundamentally isn’t one, eg bit flags and mask enums, and the latter because it ignores heterogenous math like vector-scalar operations, and how exponentiation actually results in complex numbers (remind me, what is the square root of `-1` again?).\n\n* * *\n\nNow, there is a explanation for all of this - the numerical hierarchy was one of the first things written, way back in the before times, and everything depends on it - it literally predates the modern type system capabilities. But _an explanation_ **isn’t a valid reason to not to improve things** , so I’ve gone and done something about that.\n\nIt’s not finished yet, but it is time for a sneak peek at the new mathematical hierarchy, that allows for heterogenous math and the consolidation of a great number of many operators into single symbols.\n\n# Literal expressions\n\nTo start, literals have been separated from actual numeric requirements, to allow for numeric literals to be used for other things, and to eg allow for symbolic math, bit flags, things that can be represented as a number, but don’t support addition or multiplication or such.\n\n\n    -- Integer literals\n\n    class ExpressibleByIntegerLiteral a where\n        fromIntegerLiteral :: Integer -> a\n\n    class\n        (    ExpressibleByIntegerLiteral a\n        ) => ConvertibleToIntegerLiteral a where\n        toIntegerLiteral :: a -> Integer\n\n    -- Rational literals\n\n    class ExpressibleByRationalLiteral a where\n        fromRationalLiteral :: Rational -> a\n\n    class\n        (    ExpressibleByRationalLiteral a\n        ) => ConvertibleToRationalLiteral a where\n        toRationalLiteral :: a -> Rational\n\n    -- String literals\n\n    class ExpressibleByStringLiteral a where\n        fromStringLiteral :: String -> a\n\n    class\n        (    ExpressibleByStringLiteral a\n        ) => ConvertibleToStringLiteral a where\n        toStringLiteral :: a -> String\n\n    -- List literals TBD\n\n\nThese will be used with `RebindableSyntax` later.\n\n# Arithmetic\n\nArithmetic operations have been split off, and follow a specific sub-hierarchy:\n\n\n    HeterogenousOperation =>\n        OperativeSemigroup =>\n            OperativeMonoid =>\n                OperativeGroup\n\n\nOne of the key things I have instituted is that the heterogenous operations have more colloquial names, and the homogenous operatives with associated properties have the more mathematical nomenclature - this pattern is followed library-wide\n\n## Addition and subtraction\n\n\n    class Addition a b where\n        type Sum a b :: Type\n        plus :: a -> b -> Sum a b\n\n    class (Addition a a, Sum a a ~ a) => AdditiveSemigroup a where\n        add :: a -> a -> a\n        add = plus\n\n    class AdditiveSemigroup a => AdditiveMonoid a where\n        zero :: a\n\n    class Subtraction a b where\n        type Delta a b :: Type\n        minus :: a -> b -> Delta a b\n\n    type SubtractiveSemigroup a = (Subtraction a a, Delta a a ~ a)\n\n    class (AdditiveMonoid a, SubtractiveSemigroup) => AdditiveGroup a where\n        neg :: a -> a\n        sub :: a -> a -> a\n        sub = minus\n\n\n## Multiplication and division\n\nNote that euclidean division is separate from fractional division, and that `idiv` is differentiated from `div`, although `div` may be implemented as `idiv` for eg `Integral`s.\n\n\n    class Multiplication a b where\n        type Product a b :: Type\n        times :: a -> b -> Product a b\n\n    class Division a b where\n        type Ratio a b :: Type\n        divides :: a -> b -> Ratio a b\n\n    type DivisiveSemigroup a = (Division a a, Ratio a a ~ a)\n\n    -- NOTE: Am probably going to make this heterogenous too\n    class (Division a a) => EuclideanDivision a where\n\n        type Quotient a :: Type\n        type Remainder a :: Type\n        quotRem :: a -> a -> (Quotient a, Remainder a)\n        quot    :: a -> a -> Quotient a\n        rem     :: a -> a -> Remainder a\n        idivMod  :: a -> a -> (Quotient a, Remainder a)\n        idiv     :: a -> a -> Quotient a\n        mod     :: a -> a -> Remainder a\n\n    class (Multiplication a a, Product a a ~ a) => MultiplicativeSemigroup a where\n        mul :: a -> a -> a\n        mul = times\n\n    class MultiplicativeSemigroup a => MultiplicativeMonoid a where\n        one :: a\n\n    class (MultiplicativeMonoid a, DivisiveSemigroup a) => MultiplicativeGroup a where\n        recip :: a -> a\n        div :: a -> a -> a\n        div = divides\n\n\n## Exponentiation, Logarithms, and Radication\n\nThese are currently in-progress due to the complicated nature of exponentiation being an arithmetic operation but logarithms being a transcendental operation. There is a subtle difference between ‘exponentiation’ and ‘exponential fields’.\n\n\n    class Exponentiation a b where\n        type Power a b :: Type\n        pow :: a -> b -> Power a b\n\n    class (Exponentiation a b, Power a b ~ a, Integral b) => IntegralPower a b where\n        ipow :: a -> b -> Power a b\n\n    class (Floating a, Exponentiation a a, Power a a ~ a) => FloatingPower a where\n        fpow :: a -> a -> Power a a\n\n    -- TBD - radication as the arithmetic\n\n\n## Rebindable syntax\n\nYou will probably want to use `RebindableSyntax` with this library, and to import the respective module that provides:\n\n\n    import Prelude (Eq(..), Ord(..), (>>=), (>>), fail)\n    fromInteger :: ExpressibleByIntegerLiteral a => Integer -> a\n    fromInteger = fromIntegerLiteral\n\n    fromRational :: ExpressibleByRationalLiteral a => Rational -> a\n    fromRational = fromRationalLiteral\n\n    fromString :: ExpressibleByStringLiteral a => String -> a\n    fromString = fromStringLiteral\n\n    negate :: (AdditiveGroup a) => a -> a\n    negate = neg\n    {-# INLINE negate #-}\n\n    ifThenElse :: Bool -> a -> a -> a\n    ifThenElse c t f = if c then t else f\n    {-# INLINE ifThenElse #-}\n\n\n\nThis module is incredibly useful, as we begin hijacking Haskell’s syntax to shim our own classes, thus making `0 :: Int` call `fromIntegerLiteral` from our `ExpressibleByIntegerLiteral` classes, bypassing `Num` entirely.\n\n## Operators\n\nOperators have been defined in their own module to allow for ease of selection.\n\nSuites of both heterogenous and homogenous operators are provided - here are the heterogenous operators for example:\n\n\n    infixr 8  ^\n    infixl 7  *, /\n    infixl 6  +, -\n\n    (+) :: (Addition a b) => a -> b -> Sum a b\n    a + b = plus a b\n\n    (-) :: (Subtraction a b) => a -> b -> Delta a b\n    a - b = minus a b\n\n    (*) :: (Multiplication a b) => a -> b -> Product a b\n    a * b = times a b\n\n    (/) :: (Division a b) => a -> b -> Ratio a b\n    a / b = divides a b\n\n    (^) :: (Exponentiation a b) => a -> b -> Power a b\n    a ^ b = pow a b\n\n\n\nBetween rebindable syntax and operators, we can now use standard `x + y` syntax. Now, type families do inhibit type inference, and so the heterogenous operators do not play well with un-typed literals eg `x + 0` will probably complain that it doesn’t know what type `0` is since heterogenous math means it can’t infer it from `x` - but if that is a problem, you can use the homogenous operators, which are a lot better in that regard\n\n## Rings and Fields\n\nThese are just convenient type aliases that really help make constraint declarations smaller.\n\n\n    -- Proper naturals (lacking zero) are only semirings\n    type Semiring a = (AdditiveSemigroup a, MultiplicativeSemigroup a)\n\n    -- Integers are only rings\n    type Ring a = (AdditiveGroup a, MultiplicativeMonoid a)\n\n    -- Reals are a field\n    -- Technically, a skew field or division ring - apparently there is very little\n    -- difference, and are the exact same thing for finite rings / fields\n    type NonZero a = a -- A hack for now\n    type Field a = (Ring a, MultiplicativeGroup (NonZero a))\n\n    -- TODO: Include OrderedField, etc\n\n\n## Logarithmic\n\nLogarithms / the exponential field is part of the transcendentals\n\n\n    -- NOTE: I may split into Exponential and Logarithmic\n    class (Field a, FloatingPower a) => Exponential a where\n\n        exp :: a -> a\n\n        log :: a -> a\n\n        logBase :: a -> a -> a\n        logBase b x = div (log x) (log b)\n\n\n## Trigonometry\n\nAlso part of the transcendentals.\n\n\n    class (Field a) => Trigonometric a where\n\n        pi :: a\n\n        sin :: a -> a\n        cos :: a -> a\n        tan :: a -> a\n\n        asin :: a -> a\n        acos :: a -> a\n        atan :: a -> a\n\n        atan2 :: a -> a -> a\n\n\n## Epsilon\n\n\n    class Epsilon a where\n        epsilon :: a\n        -- Near with absolute error\n        nearEq  :: a -> a -> Bool\n        -- Near with relative error\n        near    :: a -> a -> Bool\n\n\n## Signs for number lines\n\nThese are prequisites for defining our numeric types.\n\nThe ‘signed’ class is as close a definition to the ‘reals’ as I can get, when combined with `IsReal` later down the line.\n\n\n    class (Ord a) => Signed a where\n\n        type Sign a :: Type -- eg Pos | Zero | Neg, but extendable\n        -- See: UniversalSign\n\n        sign :: a -> Sign a\n\n        abs    :: a -> a\n        signum :: a -> a\n\n        isNegative :: a -> Bool\n        isZero :: a -> Bool\n        isPositive :: a -> Bool\n\n        -- NOTE: This are here instead of in their respective classes, to allow for\n        -- the classes to support the actual values\n\n        isEpsilon :: a -> Bool\n        isInfinite :: a -> Bool\n        isNegativeZero :: (Signed a) => a -> Bool\n        isPositiveZero :: (Signed a) => a -> Bool\n        isNegativeInfinite :: (Signed a) => a -> Bool\n        isPositiveInfinite :: (Signed a) => a -> Bool\n        isNegativeEpsilon :: (Signed a) => a -> Bool\n        isPositiveEpsilon :: (Signed a) => a -> Bool\n\n    data SimpleSign = Pos | Zero | Neg\n\n    data UniversalSign\n        = NegativeInfinity | Negative    | NegativeEpsilon\n        | NegativeZero     | NeutralZero | PositiveZero\n        | PositiveEpsilon  | Positive    | PositiveInfinity\n\n    -- NOTE: These have to be classes because you can have signed numbers with\n    -- unsigned infinity\n\n    -- Projectively extended reals\n    class (Signed a) => Infinity a where\n        infinity :: a\n\n    -- Hyperreals\n    -- NOTE: Infinitesimals are implicitly signed due to their definition as being\n    -- in the range between -epsilon and +epsilon\n    class (Signed a) => Infinitesimal a where\n        infinitesimal :: a\n        negativeInfinitesimal :: a\n\n    -- Extended reals if + Infinitesimal\n    class (Infinity a) => SignedInfinity a where\n\n        positiveInfinity :: a\n        positiveInfinity = infinity\n\n        negativeInfinity :: a\n\n    type Surreal a = (SignedInfinity a, Infinitesimal a)\n\n    -- Don't forget your NaN!\n    class NaN a where\n        isNaN :: a -> Bool\n\n\nYeah, you read that right - this library is powerful enough to support _hyperreals and extended reals_ **without any special requirements**.\n\n## Rounding\n\nIronically, also a prequisite for defining our numeric tpyes\n\n\n    -- TBD - some sort of `Integral (Whole a)` constraint probably\n    class ProperFraction a where\n\n        type Whole a :: Type\n        -- TODO: Maybe Fraction a?\n\n        properFraction :: a -> (Whole a, {- Fraction -} a)\n\n        whole :: a -> Whole a\n        whole = fst . properFraction\n\n        fraction :: a -> a\n        fraction = snd . properFraction\n\n    class (ProperFraction a) => Rounded a where\n        truncate :: a -> Whole a -- Towards zero\n        round    :: a -> Whole a -- Towards nearest whole\n\n    class (Rounded a) => DirectedRounded a where\n        ceiling  :: a -> Whole a -- Towards positive infinity\n        floor    :: a -> Whole a -- Towards negative infinity\n\n\n## Finally, Numbers\n\nWe now get to define the original hierarchy, stripped of non-essentials, with minor nomenclatural differences.\n\n\n    class (Signed a, Semiring a) => IsNumber a where\n        fromInteger :: Integer -> a\n\n    -- NOTE: If we rename this to IsRational, Signed can be renamed Real?\n    class (IsNumber a, Ord a) => IsReal a where\n        toRational :: a -> Rational\n\n    -- NOTE: No IsWhole class\n\n    class (IsReal a) => IsNatural a where\n        fromNatural :: Natural -> a\n\n    class (IsNatural a, Ring a) => IsIntegral a where\n        toInteger :: a -> Integer\n\n    class (IsNumber a, Field a) => IsFractional a where\n        fromRational :: Rational -> a\n\n    -- I am still pondering these two things:\n    -- class (IsIntegral a, IsFractional a) => IsRational a where\n    type IsRealFrac a = (IsReal a, DirectedRounded a {- , ProperFraction a -})\n\n    -- NOTE: This could be cleaned up further but it is not critical\n    -- NOTE: NaN, infinites negative zeroes are now handled by Signed\n    class (IsRealFrac a, Floating a) => IsFloating a where\n\n        floatRadix :: a -> Integer\n        floatDigits :: a -> Int\n        floatRange :: a -> (Int, Int)\n        decodeFloat :: a -> (Integer, Int)\n        encodeFloat :: Integer -> Int -> a\n        exponent :: a -> Int\n        significand :: a -> a\n        scaleFloat :: Int -> a -> a\n        isDenormalized :: a -> Bool\n        isIEEE :: a -> Bool\n\n\n# But why?\n\nSo I can do this:\n\n\n    class\n        ( AdditiveGroup v\n        , Field (Scalar v)\n        ) => VectorSpace v where\n\n        type Scalar v :: Type\n\n        scale :: Scalar v -> v -> v\n        default scale :: (VectorScalarMul v) => Scalar v -> v -> v\n        scale = flip times\n\n    mulS :: (VectorSpace v, VectorScalarMul v) => v -> Scalar v -> v\n    mulS = times\n\n    divS :: (VectorSpace v, VectorScalarDiv v) => v -> Scalar v -> v\n    divS = divides\n\n    type VectorScalarAdd v =\n        (Addition v (Scalar v), Sum v (Scalar v) ~ v)\n\n    type VectorScalarSub v =\n        (Subtraction v (Scalar v), Delta v (Scalar v) ~ v)\n\n    type VectorScalarMul v =\n        (Multiplication v (Scalar v), Product v (Scalar v) ~ v)\n\n    type VectorScalarDiv v =\n        (Division v (Scalar v), Ratio v (Scalar v) ~ v)\n\n\nOh! OH! Now I can do `V3 1 2 3 * 4`, but `1 * 2` still works, _with the same operator_ , **due to our homogenous math**. This is a _massive_ improvement over libraries like `linear` that _require_ using the following operators (and most other vector libraries follow similar conventions):\n\n  * (^+^)\n  * (^-^)\n  * (^*)\n  * (*^)\n  * (^/)\n  * (.-.)\n  * (.+^)\n  * (.-^)\n\n\n\nDo _you_ know what these do off the top of your head? I don’t! But _we don’t have to_ - we get to use `+`, `-`, `*`, `/`, and `^`.\n\nNot only that, but because we defined heterogenous operations, it is also trivial to make instances for addition and subtraction of affine points with vectors, which is ALSO heterogenous.\n\nAgain, we just use plus and minus and stuff:\n\n\n    class\n        ( VectorSpace (Diff p)\n        ) => AffineSpace p where\n\n        type Diff p :: Type\n\n        translate :: p -> Diff p -> p -- alt name: offset\n        default translate :: (Addition p (Diff p), Sum p (Diff p) ~ p) => p -> Diff p -> p\n        translate = plus\n\n        diff :: p -> p -> Diff p\n        default diff :: (Subtraction p p, Delta p p ~ Diff p) => p -> p -> Diff p\n        diff = minus\n\n\n# Coming Soon: The Full Geometric Suite\n\nWhat I have described so far, is only about half of what is coming down the pipeline; I have a full implementation of geometric algebra, with support for quadratic, inner, normed exterior, dual, projective, and conformal spaces, _including grades blades and multivectors_ , all of which is only made possible by the deconstruction of the original numerical hierarchy.\n\n* * *\n\nI know this has been a huge code splat with very little context, so if you are still with me, thank you for reading! I’ll try to get an actual library pushed out sooner rather than later (I need it for `memalloc`, which I need for `botan`, which I need for my game engine! Vroom!)",
  "title": "Sneak Peek: Bolt Math"
}