Reputation: 5178
I'm just starting Learn You a Haskell for Great Good, and I'm having a bit of trouble with type classes. I would like to create a function that takes any number type and forces it to be a double.
My first thought was to define
numToDouble :: Num -> Double
But I don't think that worked because Num
isn't a type, it's a typeclass (which seems to me to be a set of types). So looking at read
, shows (Read a) => String -> a
. I'm reading that as "read takes a string, and returns a thing of type a
which is specified by the user". So I wrote the following
numToDouble :: (Num n) => n -> Double
numToDouble i = ((i) :: Double)
Which looks to me like "take thing of type n (must be in the Num
typeclass, and convert it to a Double". This seems reasonable becuase I can do 20::Double
This produces the following output
Could not deduce (n ~ Double)
from the context (Num n)
bound by the type signature for numToDouble :: Num n => n -> Double
I have no idea what I'm reading. Based on what I can find, it seems like this has something to do with polymorphism?
Edit:
To be clear, my question is: Why isn't this working?
Upvotes: 3
Views: 2138
Reputation: 30227
There is no such thing as numeric casts in Haskell. When you write i :: Double
, what that means isn't "cast i
to Double
"; it's just an assertion that i
's type is Double
. In your case, however, your function's signature also asserts that i
's type is Num n => n
, i.e., any type n
(chosen by the caller) that implements Num
; so for example, n
could be Integer
. Those two assertions cannot be simultaneously true, hence you get an error.
The confusing thing is that you can say 1 :: Double
. But that's because in Haskell, a numeric literal like 1
has the same meaning as fromInteger one
, where one :: Integer
is the Integer
whose value is one.
But that only works for numeric literals. This is one of the surprising things if you come to Haskell from almost any other language. In most languages you can use expressions of mixed numeric types rather freely and rely on implicit coercions to "do what I mean"; in Haskell, on the other hand you have to use functions like fromIntegral
or fromRational
all the time. And while most statically typed languages have a syntax for casting from one numeric type to another, in Haskell you just use a function.
Upvotes: 3
Reputation: 17786
The reason you can say "20::Double" is that in Haskell an integer literal has type "Num a => a", meaning it can be any numeric type you like.
You are correct that a typeclass is a set of types. To be precise, it is the set of types that implement the functions in the "where" clause of the typeclass. Your type signature for your numToDouble correctly expresses what you want to do.
All you know about a value of type "n" in your function is that it implements the Num interface. This consists of +, -, *, negate, abs, signum and fromInteger. The last is the only one that does type conversion, but its not any use for what you want.
Bear in mind that Complex is also an instance of Num. What should numToDouble do with that? The Right Thing is not obvious, which is part of the reason you are having problems.
However lower down the type hierarchy you have the Real typeclass, which has instances for all the more straightforward numerical types you probably want to work with, like floats, doubles and the various types of integers. That includes a function "toRational" which converts any real value into a ratio, from which you can convert it to a Double using "fromRational", which is a function of the "Fractional" typeclass.
So try:
toDouble :: (Real n) => n -> Double
toDouble = fromRational . toRational
But of course this is actually too specific. GHCI says:
Prelude> :type fromRational . toRational
fromRational . toRational :: (Fractional c, Real a) => a -> c
So it converts any real type to any Fractional type (the latter covers anything that can do division, including things that are not instances of Real, like Complex) When messing around with numeric types I keep finding myself using it as a kind of generic numerical coercion.
Edit: as leftaroundabout says,
realToFrac = fromRational . toRational
Upvotes: 9
Reputation: 62818
Just to be 100% clear, the problem is
(i) :: Double
This does not convert i
to a Double
, it demands that i
already is a Double
. That isn't what you mean at all.
The type signature for your function is correct. (Or at least, it means exactly what you think it means.) But your function's implementation is wrong.
If you want to convert one type of data to another, you have to actually call a function of some sort.
Unfortunately, Num
itself only allows you to convert an Integer
to any Num
instance. You're trying to convert something that isn't necessarily an Integer
, so this doesn't help. As others have said, you probably want fromRational
or similar...
Upvotes: 3
Reputation: 120711
You can't "convert" anything per se in Haskell. Between specific types, there may be the possibility to convert – with dedicated functions.
In your particular example, it certainly shouldn't work. Num
is the class1 of all types that can be treated as numerical types, and that have numerical values in them (at least integer ones, so here's one such conversion function fromInteger
).
But these types can apart from that have any other stuff in them, which oftentimes is not in the reals and can thus not be approximated by Double
. The most obvious example is Complex
.
The particular class that has only real numbers in it is, suprise, called Real
. What is indeed a bit strange is that its method is a conversion toRational
, since the rationals don't quite cover the reals... but they're dense within them, so it's kind of ok. At any rate, you can use that function to implement your desired conversion:
realToDouble :: Real n => n -> Double
realToDouble i = fromRational $ toRational i
Incidentally, that combination fromRational . toRational
is already a standard function: realToFrac
, a bit more general.
Calling type classes "sets of types" is kind of ok, much like you can often get away without calling any kind of collection in maths a set – but it's not really correct. The most problematic thing is, you can't really say some type is not in a particular class: type classes are open, so at any place in a project you could declare an instance for some type to a given class.
Upvotes: 3