1

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
2
  • Those types look pretty different to me - one of them's a -> a and the other's a -> b. Plus, type classes don't allow you to choose behaviour based on which instances are in scope - someone could add an instance later. It's all resolved at compile time Commented Sep 22, 2016 at 0:07
  • 1
    This looks as an XY problem. Why do you need those two functions to share the same name? Commented Sep 22, 2016 at 8:58

3 Answers 3

4

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
Sign up to request clarification or add additional context in comments.

Comments

2

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.

3 Comments

"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" - this is the right answer. I was trying to articulate something along those lines in an edit to my answer but I needn't bother now :)
I propose a solution for which trunc (3 :: Int) infers the type Int rather than Integral b => b, and for which trunc (3 :: Double) is still polymorphic over the Integral instance; you may like it.
@DanielWagner ah cool, that is nice, addresses the performance issue I was worried about.
2

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.

3 Comments

That's pretty clever!
You haven't really done anything... you still need to decide somewhere whether to construct a T1 or a T2, and at that point you might as well decide whether to say trunc or id
@luqui I agree. It's not very idiomatic Haskell to try to dispatch runtime behaviour based on type class availability (which is how I'm interpreting OP's question)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.