I've been doing some reading and some watching of tutorials of Haskell.. However, I can't really seem to get a grasp of it..
So I decided to try to make a very easy program.. Conversion from celsius to kelvin, and kelvin to celsius.. But I run into a problem with types.. Here is my functions:
-- conversions kelvinToCelsius x = x - 273.15 celsiusToKelvin x = x + 273.15 kelvinCelsius x y = if y == 'c' then celsiusToKelvin x else if y == 'k' then kelvinToCelsius x else putStrLn "c or k?"
I guess the problem is that I dont know how to declare the types, or am I wrong?
You are mixing types in your nested if-else. You have branches doing the conversions and producing numbers, and a branch of type IO () that prints a message but does not produce any interesting value. So you probably get a type error pointing out this mismatch between IO () and one of Float, Double or Fractional a, but you forgot to mention that.
You are mixing the conversions (pure computations) with IO. That is a bit harder to do in Haskell than in your average imperative language. You might say that's a good thing because it sort of pushes you to cleanly separate the two tasks into separate functions.
It might be best stick to pure functions (without IO) for now and play with those using ghci. When you are comfortable with the basics, try to add some simple IO. A simple fix (with type signature):
convertTemp1 :: Char -> Double -> Double convertTemp1 'k' t = t - 273.15 convertTemp1 'c' t = t + 273.15 convertTemp1 _ _ = error "c or k?"
This still produces a message when the unit to convert from is not 'c' or 'k' -- it generates a runtime error.
Once you know about data types, you can do better:
data TempUnit = Kelvin | Celsius convertTemp2 :: TempUnit -> Double -> Double convertTemp2 Kelvin t = t - 273.15 convertTemp2 Celsius t = t + 273.15
To turn this into a standalone program with IO, add a main function (which needs to have type IO ()):
data TempUnit = Kelvin | Celsius deriving Read -- need instance of Read class to use readLn main = do t <- readLn unit <- readLn print (convertTemp2 unit t)
By adding 'deriving Read' to the data type declaration we can keep the main function pretty short (but you will get ugly parse exceptions). Using combinators from Control.Applicative, we can even make it a oneliner:
import Control.Applicative main = flip convertTemp2 <$> readLn <*> readLn >>= print
The type system figures out the right types for each readLn call. Example session in ghci (you could also compile this into an executable since we have a main function):
*Main> main 14 Celcius *** Exception: user error (Prelude.readIO: no parse) *Main> main 14 Celsius 287.15
This is starting to look like a tutorial... I don't know what and how much you have read already, but I think http://learnyouahaskell.com/ is a nice book (although I haven't read the whole thing).
*Disclaimer this isn't exactly how the compiler works, but it will help you understand the problem*
So, to add to Raynman's post, the more fundamental reason that it won't work is because you are trying to return two different types in "kelvinCelsius"
When the compiler tries to figure out what the type of kelvinCelsius is, it starts off with something like the following as the type of your function:
t -> t1 -> t2
Because it knows that the function is going to have two "parameters" (actually all functions have one parameter in Haskell, but let's ignore that for now).
Then it sees that you have "y == 'c'", so it looks at the type of (==), which is:
Eq a => a -> a -> Bool
Then it looks at the 'c', which is of type Char, and checks to see if it can take the place of an 'a' in that type signature.
'c' is of type Char, and the Char type is an instance of the Eq type class, which means that it can indeed take the place of one of those 'a' variables.
Next it sees that you used y, which was one of your parameters, so it modifies the original type, because it is now aware that y *must* be of type Char, so your function now has the type:
t -> Char -> t2
Next it starts looking at the return types of your if expression, i.e.
Okay, so that means the return type must be the same type as celsiusToKelvin's return type, which is: Fractional a => a
Furthermore it sees that you used x, which was the first parameter, so your type is now:
Fractional a => a -> Char -> a
We can ignore the rest of your function for now except the last part: putStrLn "c or k?"
This is also being returned by your function, so the same process as above applies, and the return type of putStrLn *must* be the return type of kelvinToCelsius
putStrLn has the type:
String -> IO ()
But we already said the return type was
Fractional a => a
So either there is a contradiction (a return type error), or we have to somehow make these two types "the same". The second option is unavailable, so the first is the only valid conclusion the compiler can make from your code.
The solution is to make the return types uniform. One possible solution is to call a function on both of your Num a => a types that returns IO (), but doing that isn't exactly a good idea. A better idea would be to refactor it in terms of a data type such as Either or Maybe which is able to signal error conditions. Or an even better idea: you can define a data type (as Raynman's post illustrates), with two constructors for either Celsius or Kelvin, then it won't be possible to call the function with an incorrect unit.
Last edited by Nisstyre56 (2013-04-25 02:05:52)
In Zen they say: If something is boring after two minutes, try it for four. If still boring, try it for eight, sixteen, thirty-two, and so on. Eventually one discovers that it's not boring at all but very interesting.
~ John Cage
I very much appreciate your help. Both of you cleared a lot up for me. Been doing some reading on http://book.realworldhaskell.org/ and I now understand the type system better.