Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type constraints become huge and unreadable for simple code

Mind the following code:

-- A n-dimensional axis aligned bounding box.
data AABB v a = AABB {
    aabbMin :: !(v a), 
    aabbMax :: !(v a)
    } deriving (Show)

-- `v` is a container, representing the number of dimensions. Ex:
-- type Box2DFloat = AABB V2 Float
-- type Box4DFloat = AABB V4 Float 

-- A n-dimensional ray.
data Ray v a = Ray {
    rayPos :: !(v a),
    rayDir :: !(v a)
    } deriving (Show)

-- Traces a n-d ray through a n-d box, returns 
-- the intersection indexes (tmin, tmax).
intersectAABB 
    :: (Foldable f, 
        Metric f, 
        Ord a, 
        Num (f a), 
        Fractional (f a), 
        Floating a) 
    => Ray f a 
    -> AABB f a 
    -> [a]
intersectAABB (Ray rayPos rayDir) (AABB aabbMin aabbMax) 
    = [tmin, tmax] where
        t1   = (aabbMin - rayPos) / rayDir
        t2   = (aabbMax - rayPos) / rayDir
        tmin = foldr1 max $ liftI2 min t1 t2
        tmax = foldr1 min $ liftI2 max t1 t2

That is an usual Ray→AABB intersection function, and it is pretty simple and clean, except for the type signature, which is almost bigger than the function itself! Someone suggested I could use kind constraints that "encapsule my needs" to make it less verbose, but I can't find a kind constraint that "encapsules my needs" properly. "My needs", on this case, are basically "the type acts like a number should". So, on my mind, the following would make sense:

class Real a where
    ... anything I want a real number to do ...

instance Real Float where
    ...

instance (Real a) => Real (V2 a) where
    ...

instance (Real a) => Real (V3 a) where
    ...

type AABB a = V2 a
type Ray a  = V2 a

type Box2DFloat = AABB (V2 Float)
type Box4DFloat = AABB (V4 Float)
type Ray2D a = Ray (V2 a)
type Ray3DRatio = Ray (V3 Ratio)
... etc ...

That way, my signatures would become, simply:

intersectAABB :: (Real r, Real s) => Ray r -> AABB r -> [s]

Which looks much better. But, if nobody using Haskell has bothered to define such class, there should be a reason. What is the reason there is no "Real" class, and, if defining such a class would be a bad idea, what is the proper solution to my issue?

like image 899
MaiaVictor Avatar asked Feb 11 '23 02:02

MaiaVictor


1 Answers

Use constraint synonyms:

{-# LANGUAGE ConstraintKinds #-}

type ConstraintSynonym f a = (
  Foldable f,
  Metric f,
  Ord a, 
  Num (f a), 
  Fractional (f a), 
  Floating a)

With ConstraintKinds, lifted tuples can be used to express conjunction of constraints (and () can refer to the trivially satisfied constraint). You can now use ConstraintSynonym instead of the big tuple of constraints in annotations.

like image 143
András Kovács Avatar answered Feb 12 '23 23:02

András Kovács