Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • Home
  • SEARCH
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 6368645
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: May 25, 20262026-05-25T00:42:07+00:00 2026-05-25T00:42:07+00:00

Context I’m writing a Haskell module that represents SI prefixes: module Unit.SI.Prefix where Each

  • 0

Context

I’m writing a Haskell module that represents SI prefixes:

module Unit.SI.Prefix where

Each SI prefix has a corresponding data type:

data Kilo = Kilo deriving Show
data Mega = Mega deriving Show
data Giga = Giga deriving Show
data Tera = Tera deriving Show

-- remaining prefixes omitted for brevity

Problem

I would like to write a function that, when applied with two SI prefixes, determines statically which of the two prefixes is smaller. For example:

-- should compile:
test1 = let Kilo = smaller Kilo Giga in ()
test2 = let Kilo = smaller Giga Kilo in ()

-- should fail to compile:
test3 = let Giga = smaller Kilo Giga in ()
test4 = let Giga = smaller Giga Kilo in ()

Initial Solution

Here’s a solution that uses a type class together with a functional dependency:

{-# LANGUAGE FunctionalDependencies #-}                                                                                         
{-# LANGUAGE MultiParamTypeClasses #-}

class Smaller a b c | a b -> c where smaller :: a -> b -> c

instance Smaller Kilo Kilo Kilo where smaller Kilo Kilo = Kilo
instance Smaller Kilo Mega Kilo where smaller Kilo Mega = Kilo
instance Smaller Kilo Giga Kilo where smaller Kilo Giga = Kilo
instance Smaller Kilo Tera Kilo where smaller Kilo Tera = Kilo

instance Smaller Mega Kilo Kilo where smaller Mega Kilo = Kilo
instance Smaller Mega Mega Mega where smaller Mega Mega = Mega
instance Smaller Mega Giga Mega where smaller Mega Giga = Mega
instance Smaller Mega Tera Mega where smaller Mega Tera = Mega

instance Smaller Giga Kilo Kilo where smaller Giga Kilo = Kilo
instance Smaller Giga Mega Mega where smaller Giga Mega = Mega
instance Smaller Giga Giga Giga where smaller Giga Giga = Giga
instance Smaller Giga Tera Giga where smaller Giga Tera = Giga

instance Smaller Tera Kilo Kilo where smaller Tera Kilo = Kilo
instance Smaller Tera Mega Mega where smaller Tera Mega = Mega
instance Smaller Tera Giga Giga where smaller Tera Giga = Giga
instance Smaller Tera Tera Tera where smaller Tera Tera = Tera

The above solution appears to solve the problem correctly, however it has a downside: the number of type class instances is quadratic w.r.t. the number of types.

Question

Is there any way to reduce the number of type class instances to be linear w.r.t. the number of types, perhaps by exploiting symmetry?

It may be that it’s more appropriate to use Template Haskell here, in which case, feel free to suggest that as a solution.

Thanks!

  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-05-25T00:42:08+00:00Added an answer on May 25, 2026 at 12:42 am

    It could probably be argued that TH is more appropriate in cases like this. That said, I’ll do it with types anyhow.

    The problem here is that everything is too discrete. You can’t iterate through the prefixes to find the right one, and you’re not expressing the transitivity of the ordering you want. We can solve it by either route.

    For a recursive solution, we first create natural numbers and boolean values at the type level:

    {-# LANGUAGE TypeOperators #-}
    {-# LANGUAGE UndecidableInstances #-}
    {-# LANGUAGE TypeFamilies #-}
    
    data No = No deriving (Show)
    data Yes = Yes deriving (Show)
    
    newtype S nat = Succ nat deriving (Show)
    data Z = Zero deriving (Show)
    
    type Zero  = Z
    type One   = S Zero
    type Two   = S One
    type Three = S Two
    

    A bit of simple arithmetic:

    type family Plus x y :: *
    type instance Plus x Z = x
    type instance Plus Z y = y
    type instance Plus (S x) (S y) = S (S (Plus x y))
    
    type family Times x y :: *
    type instance Times x Z = Z
    type instance Times x (S y) = Plus x (Times y x)
    

    A “less than or equal to” predicate, and a simple conditional function:

    type family IsLTE n m :: *
    type instance IsLTE Z Z = Yes
    type instance IsLTE (S m) Z = No
    type instance IsLTE Z (S n) = Yes
    type instance IsLTE (S m) (S n) = IsLTE m n
    
    type family IfThenElse b t e :: *
    type instance IfThenElse Yes t e = t
    type instance IfThenElse No t e = e
    

    And conversions from the SI prefixes to the magnitude they represent:

    type family Magnitude si :: *
    type instance Magnitude Kilo = Three
    type instance Magnitude Mega = Three `Times` Two
    type instance Magnitude Giga = Three `Times` Three
    

    …etc.

    Now, to find the smaller prefix, you can do this:

    type family Smaller x y :: *
    type instance Smaller x y = IfThenElse (Magnitude x `IsLTE` Magnitude y) x y
    

    Given that everything here has a one-to-one correspondence between the type and the single nullary constructor inhabiting it, this can be translated to the term level using a generic class like this:

    class TermProxy t where term :: t
    instance TermProxy No where term = No
    instance TermProxy Yes where term = Yes
    {- More instances here... -}
    
    smaller :: (TermProxy a, TermProxy b) => a -> b -> Smaller a b
    smaller _ _ = term
    

    Filling in the details as needed.


    The other approach involves using functional dependencies and overlapping instances to write a generic instance to fill in gaps–so you could write specific instances for Kilo < Mega, Mega < Giga, etc. and let it deduce that this implies Kilo < Giga as well.

    This gets deeper into treating functional dependencies as what they are–a primitive logic programming language. If you’ve ever used Prolog, you should have the rough idea. In some ways this is nice, because you can let the compiler figure things out based on a more declarative approach. On the other hand it’s also kind of terrible because…

    • Instances are chosen without looking at constraints, only the instance head.
    • There’s no backtracking to search for a solution.
    • To express this sort of thing you have to turn on UndecidableInstances because of GHC’s very conservative rules about what it knows will terminate; but you then have to take care not to send the type checker into an infinite loop. For instance, it would be very easy to do that by accident given instances like Smaller Kilo Kilo Kilo and something like (Smaller a s c, Smaller t b s) => Smaller a b c–think about why.

    Fundeps and overlapping instances are strictly more powerful than type families, but they’re clumsier to use overall, and feel somewhat out of place compared to the more functional, recursive style the latter uses.


    Oh, and for completeness’ sake, here’s a third approach: This time, we’re abusing the extra power that overlapping instances gives us to implement a recursive solution directly, rather than by converting to natural numbers and using structural recursion.

    First, reify the desired ordering as a type-level list:

    data MIN = MIN deriving (Show)
    data MAX = MAX deriving (Show)
    
    infixr 0 :<
    data a :< b = a :< b deriving (Show)
    
    siPrefixOrd :: MIN :< Kilo :< Mega :< Giga :< Tera :< MAX
    siPrefixOrd = MIN :< Kilo :< Mega :< Giga :< Tera :< MAX
    

    Implement an equality predicate on types, using some overlapping shenanigans:

    class (TypeEq' () x y b) => TypeEq x y b where typeEq :: x -> y -> b
    instance (TypeEq' () x y b) => TypeEq x y b where typeEq _ _ = term
    
    class (TermProxy b) => TypeEq' q x y b | q x y -> b
    instance (b ~ Yes) => TypeEq' () x x b 
    instance (b ~ No) => TypeEq' q x y b 
    

    The alternate “is less than” class, with the two easy cases:

    class IsLTE a b o r | a b o -> r where
        isLTE :: a -> b -> o -> r
    
    instance (IsLTE a b o r) => IsLTE a b (MIN :< o) r where
        isLTE a b (_ :< o) = isLTE a b o
    
    instance (No ~ r) => IsLTE a b MAX r where
        isLTE _ _ MAX = No
    

    And then the recursive case, with an auxiliary class used to defer the recursive step based on case analysis of the type-level boolean:

    instance ( TypeEq a x isA, TypeEq b x isB
             , IsLTE' a b isA isB o r
             ) => IsLTE a b (x :< o) r where
        isLTE a b (x :< o) = isLTE' a b (typeEq a x) (typeEq b x) o
    
    class IsLTE' a b isA isB xs r | a b isA isB xs -> r where
        isLTE' :: a -> b -> isA -> isB -> xs -> r
    
    instance (Yes ~ r) => IsLTE' a b Yes Yes xs r where isLTE' a b _ _ _ = Yes
    instance (Yes ~ r) => IsLTE' a b Yes No xs r where isLTE' a b _ _ _ = Yes
    instance (No ~ r) => IsLTE' a b No Yes xs r where isLTE' a b _ _ _ = No
    instance (IsLTE a b xs r) => IsLTE' a b No No xs r where
        isLTE' a b _ _ xs = isLTE a b xs
    

    In essence, this takes a type-level list and two arbitrary types, then walks down the list and returns Yes if it finds the first type, or No if it finds the second type or hits the end of the list.

    This is actually kind of buggy (you can see why if you think about what happens if one or both types aren’t in the list), as well as prone to failing–direct recursion like this uses a context reduction stack in GHC that is very shallow, so it’s easy to exhaust it and get a type-level stack overflow (ha ha, yes, the joke writes itself) instead of the answer you wanted.

    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Related Questions

The Context At the moment, I have a JTextArea that has been created like
Context: I have a WPF App that uses certain unmanaged DLLs in the D:\WordAutomation\MyApp_Source\Executables\MyApp
Context: C# 3.0, .Net 3.5 Suppose I have a method that generates random numbers
In context of recent trends in interviews i have noticed that this question arises
Context: I'm in charge of running a service written in .NET. Proprietary application. It
Context: So, I am attempting to build a ridiculously complex domain model. Talking with
Context : programming a c/c++ win32-mfc library How to know whether we are in
Context PHP 5.3.x Overview After doing a code-review with an associate who uses both
For context, I am something of an emacs newbie. I haven't used it for
Some context upfront: Imagine a 200+ developers company finally setting up a more or

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.