Defining a Function for Multiple Types

How is a function defined for different types in Haskell? Given

func :: Integral a => a -> a
func x = x
func' :: (RealFrac a , Integral b) => a -> b
func' x = truncate x

How could they be combined into one function with the signature

func ::  (SomeClassForBoth a, Integral b) => a -> b

Upvotes: 1

Views: 3552

Answers (3)

Daniel Wagner
Daniel Wagner

Reputation: 153172

With a typeclass.

class    TowardsZero a      where towardsZero :: Integral b => a -> b
instance TowardsZero Int    where towardsZero = fromIntegral
instance TowardsZero Double where towardsZero = truncate
-- and so on

Possibly a class with an associated type family constraint is closer to what you wrote (though perhaps not closer to what you had in mind):

{-# LANGUAGE TypeFamilies #-}
import GHC.Exts

class TowardsZero a where
    type RetCon a b :: Constraint
    towardsZero :: RetCon a b => a -> b

instance TowardsZero Int where
    type RetCon Int b = Int ~ b
    towardsZero = id

instance TowardsZero Double where
    type RetCon Double b = Integral b
    towardsZero = truncate

-- and so on

Upvotes: 4

Benjamin Hodgson
Benjamin Hodgson

Reputation: 44644

I don't recommend this, but you can hack at it with a GADT:

data T a where
    T1 :: a -> T a
    T2 :: RealFrac a => a -> T b

func :: Integral a => T a -> a
func (T1 x) = x
func (T2 x) = truncate x

The T type says, "Either you already know the type of the value I'm wrapping up, or it's some unknown instance of RealFrac". The T2 constructor existentially quantifies a and packs up a RealFrac dictionary, which we use in the second clause of func to convert from (unknown) a to b. Then, in func, I'm applying an Integral constraint to the a which may or may not be inside the T.

Upvotes: 2

luqui
luqui

Reputation: 60513

This is known as ad hoc polymorphism, where you execute different code depending on the type. The way this is done in Haskell is using typeclasses. The most direct way is to define a new class

class Truncable a where
    trunc :: Integral b => a -> b

And then you can define several concrete instances.

instance Truncable Integer where trunc = fromInteger
instance Truncable Double where trunc = truncate

This is unsatisfying because it requires an instance for each concrete type, when there are really only two families of identical-looking instances. Unfortunately, this is one of the cases where it is hard to reduce boilerplate, for technical reasons (being able to define "instance families" like this interferes with the open-world assumption of typeclasses, among other difficulties with type inference). As a hint of the complexity, note that your definition assumes that there is no type that is both RealFrac and Integral, but this is not guaranteed -- which implementation should we pick in this case?

There is another issue with this typeclass solution, which is that the Integral version doesn't have the type

trunc :: Integral a => a -> a

as you specified, but rather

trunc :: (Integral a, Integral b) => a -> b

Semantically this is not a problem, as I don't believe it is possible to end up with some polymorphic code where you don't know whether the type you are working with is Integral, but you do need to know that when it is, the result type is the same as the incoming type. That is, I claim that whenever you would need the former rather than the latter signature, you already know enough to replace trunc by id in your source. (It's a gut feeling though, and I would love to be proven wrong, seems like a fun puzzle)

There may be performance implications, however, since you might unnecessarily call fromIntegral to convert a type to itself, and I think the way around this is to use {-# RULES #-} definitions, which is a dark scary bag of complexity that I've never really dug into, so I don't know how hard or easy this is.

Upvotes: 2

Related Questions