CNJ
CNJ

Reputation: 31

Defining a function that behaves differently according to whether the argument is Integral or Fractional

A sort-of solution is to write out a bunch of instances by hand,

class Halve a where
  halve :: a -> a

instance Halve Int where
  halve = (`div` 2)
instance Halve Integer where
  halve = (`div` 2)
instance Halve Float where
  halve = (/ 2)
instance Halve Double where
  halve = (/ 2)
-- ... etc.

However some instances will inevitably be missed, such as user-defined ones. My attempted real solution is this non-working code:

{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}

class Halve a where
  halve :: a -> a

instance Integral a => Halve a where
  halve = (`div` 2)
instance Fractional a => Halve a where
  halve = (/ 2)

which gives the error

Duplicate instance declarations:
  instance Integral a => Halve a
    -- Defined at ...
  instance Fractional a => Halve a
    -- Defined at ...

I haven't found how to express this correctly, if it can be done.

Upvotes: 3

Views: 96

Answers (4)

Daniel Wagner
Daniel Wagner

Reputation: 153102

One lightweight way would be to define two new types, thus:

newtype FracWrap a = F a deriving Fractional
newtype IntWrap  a = I a deriving Integral

instance Fractional a => Halve (FracWrap a) where halve = (/2)
instance Integral   a => Halve (IntWrap  a) where halve = (`div`2)

Then, for foo which takes a Halve-constrained argument, one has the relatively lightweight foo.F and foo.I to indicate whether one wants to use the Fractional or Integral version of foo.

This is relatively nice in terms of API design, as the API designer need not offer two version of everything; just one Halve-specific version of everything, and two adapters.

Upvotes: 2

MathematicalOrchid
MathematicalOrchid

Reputation: 62848

Try making the function to halve a value be a specific input to your code.

foobar :: (Ord x) => (x -> x) -> x -> x -> ...
foobar halve x1 x2 = ...

It's kind of annoying, but hopefully you only have to specify this once, at the very top of your algorithm. Any child functions can just receive the parameter passed on.

Another alternative would be to do something like

data Halvable x = Halvable {value :: x, halve :: x -> y}

halfInt :: Integral x => x -> Halvable x
halfInt x = Halvable x (`div` 2)

halfFrac :: Fractional x => x -> Halvable x
halfFrac x = Halvable x (/ 2)

Upvotes: 2

dfeuer
dfeuer

Reputation: 48611

What you're asking for is pretty much horrible. What is the meaning of this class? It's also impossible to do it with significantly less boilerplate than your working partial solution. The problem is that GHC, even with overlapping or incoherent (barf) instances will always commit to an instance based on the part of the instance head to the right of the =>.

GHC never acts on the belief that a type is not an instance of a class except to produce an error message.

Upvotes: 3

Bakuriu
Bakuriu

Reputation: 101989

You can't:

data Hell = YouCantDoThat

instance Integral Hell

instance Fractional Hell

There is no rule saying that an Integral cannot be a Fractional or viceversa. And if that's the case how do you choose? This is exactly what the error is telling you.

Upvotes: 4

Related Questions