开发者

Writing a function with a universally-quantified return type

If I write

foo :: (Num a) => a
foo = 42

GHC happily accepts it, but if I write

bar :: (Num a) => a
bar = (42 :: Int)

it tells me that the expected type a doesn't match the inferred type Int. I don't quite understand why, since Int is an ins开发者_运维知识库tance of the class Num that a stands for.

I'm running into this same situation while trying to write a function that, boiled down to the core of the problem, looks roughly like this:

-- Note, Frob is an instance of class Frobbable
getFrobbable :: (Frobbable a) => Frob -> a
getFrobbable x = x

Is it possible to write a function like this? How can I make the result compatible with the type signature?


You are treating typeclass constraints as if they were subtype constraints. This is a common thing to do, but in fact they only coincide in the contravariant case, that is, when concerning function arguments rather than results. The signature:

bar :: (Num a) => a

Means that the caller gets to choose the type a, provided that it is an instance of Num. So here, the caller may choose to call bar :: Int or bar :: Double, they should all work. So bar :: (Num a) => a must be able to construct any kind of number, bar does not know what specific type was chosen.

The contravariant case is exactly the same, it just corresponds with OO programmers' intuition in this case. Eg.

baz :: (Num a) => a -> Bool

Means that the caller gets to choose the type a, again, and again baz does not know what specific type was chosen.

If you need to have the callee choose the result type, just change the signature of the function to reflect this knowledge. Eg.:

bar :: Int

Or in your getFrobbable case, getFrobbable :: Frob -> Frob (which makes the function trivial). Anywhere that a (Frobbable a) constraint occurs, a Frob will satisfy it, so just say Frob instead.

This may seem awkward, but it's really just that information hiding happens at different boundaries in functional programming. There is a way to hide the specific choice, but it is uncommon, and I consider it a mistake in most cases where it is used.


One way to get the effect your sample seems to be going for

I first want to describe a way to accomplish what it appears you're going for. Let's look at your last code sample again:

-- Note, Frob is an instance of class Frobbable
getFrobbable :: (Frobbable a) => Frob -> a
getFrobbable x = x

This is essentially a casting operation. It takes a Frob and simply forgets what it is, retaining only the knowledge that you've got an instance of Frobbable.

There is an idiom in Haskell for accomplishing this. It requires the ExistentialQuantification extension in GHC. Here is some sample code:

{-# LANGUAGE ExistentialQuantification #-}
module Foo where

class Frobbable a where
  getInt :: a -> Int

data Frob = Frob Int

instance Frobbable Frob where
  getInt (Frob i) = i

data FrobbableWrapper = forall a . Frobbable a => FW a

instance Frobbable FrobbableWrapper where
  getInt (FW i) = getInt i

The key part is the FrobbableWrapper data structure. With it, you can write the following version of your getFrobbable casting function:

getFrobbable :: Frobbable a => a -> FrobbableWrapper
getFrobbable x = FW x

This idiom is useful if you want to have a heterogeneous list whose elements share a common typeclass, even though they may not share a common type. For instance, while Frobbable a => [a] wouldn't allow you to mix different instances of Frobbable, the list [FrobbableWrapper] certainly would.

Why the code you posted isn't allowed

Now, why can't you write your casting operation as-is? It's all about what could be accomplished if your original getFrobbable function was permitted to type check.

The equation getFrobbable x = x really should be thought of as an equation. x isn't being modified in any way; thus, neither is its type. This is done for the following reason:

Let's compare getFrobbable to another object. Consider

anonymousFrobbable :: Frobbable a => a
anonymousFrobbable = undefined

(Code involving undefined are a great source of awkward behavior when you want to really push on your intuition.)

Now suppose someone comes along and introduces a data definition and a function like

data Frob2 = Frob2 Int Int

instance Frobbable Frob2 where
  getInt (Frob2 x y) = y

useFrobbable :: Frob2 -> [Int]
useFrobbable fb2 = []

If we jump into ghci we can do the following:

*Foo> useFrobbable anonymousFrobbable 
[]

No problems: the signature of anonymousFrobbable means "You pick an instance of Frobbable, and I'll pretend I'm of that type."

Now if we tried to use your version of getFrobbable, a call like

useFrobbable (getFrobbable someFrob)

would lead to the following:

  1. someFrob must be of type Frob, since it is being given to getFrobbable.
  2. (getFrobbable someFrob) must be of type Frob2 since it is being given to useFrobbable
  3. But by the equation getFrobbable someFrob = someFrob, we know that getFrobbable someFrob and someFrob have the same type.

Thus the system concludes that Frob and Frob2 are the same type, even though they are not. Hence this reasoning is unsound, which is ultimately the type of rational behind why the version of getFrobbable you posted doesn't type-check.


Also worth noting is that the literal 42 actually stands for fromInteger (42 :: Integer), which really has type (Num a) => a. See the Haskell Report on numeric literals.

A similar mechanism works for floating point numbers and you can get GHC to use overloaded string literals, but for other types there is (to my knowledge) no way to do that.


I am a teensy bit confused as to what you're trying to do, but if you just want the caller's use of the return value to drive instance selection, then that's normal typeclasses at work.

data Frob = Frob Int Float

class FrobGettable a where
    getFrobbable :: Frob -> a

instance FrobGettable Int where
    getFrobbable (Frob x _) = x

instance FrobGettable Float where
    getFrobbable (Frob _ y) = y

instance FrobGettable Char where
    getFrobbable _ = '!'

frob = Frob 10 3.14

test1 :: Float
test1 = getFrobbable frob * 1.1

test2 :: Int
test2 = getFrobbable frob `div` 4

test3 = "Hi" ++ [getFrobbable frob]

We can use GHCi to see what we have,

*Main> :t getFrobbable
getFrobbable :: (FrobGettable a) => Frob -> a
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜