Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell Typeclasses (Float does not imply Floating?)

Tags:

haskell

toFloat :: (Floating a) => String -> a
toFloat s = read s :: Float

main = print (toFloat "1")

Gives me the error:

Could not deduce (a ~ Float)
from the context (Floating a)

I'm sure I'm missing something basic, but it seems like my toFloat should always return a Float and that Float should imply Floating.

like image 495
nottombrown Avatar asked Dec 30 '12 06:12

nottombrown


4 Answers

The type signature promises the result will be any instance of the Floating class the caller wants. The implementation says "Know what? Nevermind on that promise that it can be any type - let's just make it a Float".

Then the compiler comes along and says "Whoa! You're not returning the type you promised you would." Except that it tried really hard to make your type signature and your implementation match up. It said to itself "If this was constrained somehow, such that a was always the same thing as Float, this would be correct." It really wanted to find a way your code was correct. Well, the way to write such a constraint is using ~, the type equality operator. A constraint of (a ~ Float) would mean "a is the same type as Float". So the compiler checks the context you provided in the type signature, and it fails to find that constraint. And it runs out of ways to make your type signature and implementation work together, gives up, and reports an error.

Unfortunately, the error it reports is a bit opaque due to how much effort it put into trying to make your code work. Just the tiniest change, adding one little constraint, would have made it right. So it reports that that constraint wasn't present. But it doesn't report why it was looking for that constraint, making the whole thing a bit unclear if you haven't seen it before.

like image 104
Carl Avatar answered Nov 01 '22 04:11

Carl


This question or something very similar to it gets asked on a regular basis. (That's not a complaint, I'm just pointing out that you're not the only person confused by this.)

In an OO language, you can say "this function returns something that implements X". The function can then return whatever the hell it feels like returning, so long as it does in fact implement X.

Haskell does not work like that. If you say "this function returns something that implements X", then the function must be able to yield any possible type that implements X!

The key difference is this: In an OO language, the function decides what type to return (within the specified constraints). In Haskell, the caller decides what type to return (again, within the stipulated constraints).

Once you comprehend this key difference, the rest is fairly self-evident.

Again, a lot of people seem to misunderstand this part. We should probably mention it more in tutorials and stuff, because it seems to be a VFAQ...

like image 44
MathematicalOrchid Avatar answered Nov 01 '22 05:11

MathematicalOrchid


You are saying toFloat can return any type belonging to Floating typeclass but you are restricting it to Float, which is wrong. Your function is polymorphic in a so you just can not return an instance of Floating, it should be able to work with all the instances.

Otherway you can understand this by

toFloat :: (Read a,Floating a) => String -> a
toFloat s = read s

In ghci

*Main> :t toFloat "12.1"
toFloat "12.1" :: (Floating a, Read a) => a
*Main> :t (toFloat "12.1" :: Float)
(toFloat "12.1" :: Float) :: Float
*Main> :t (toFloat "12.1" :: Double)
(toFloat "12.1" :: Double) :: Double

Since it returns type belonging to typeclass Floating you should be able to convert it to any type (belonging to Floating) by providing explicit type signatures after function is applied. On the other hand remember your case when you are explicitly returning Float now you can not just say I expect Double from this function because that can not happen without explicit conversion.

Another way to understand how horrible your assumption is consider function read

read :: Read a => String -> a

Now here according to you, you can just return say Int for everything because Int has an instance for Read. Now you can understand what will happen if you do something like

read "12" +  (1.2 :: Double)
like image 35
Satvik Avatar answered Nov 01 '22 06:11

Satvik


This is as simple as you could have it:

-- The simplest.                                                                                                                                               
toFloat :: String -> Float
toFloat = read

-- More generalized.                                                                                                                                           
toFloat' :: (Floating a, Read a) => String -> a
toFloat' = read
like image 23
Colin Woodbury Avatar answered Nov 01 '22 04:11

Colin Woodbury