4

I'm learning Haskell, and these are my first steps in trying to understand the syntax. I'm having a very hard time, and the language reference is not helpful, unfortunately.

I tried to do :type (line_length 1 2 3 4) and I have red the reference on printf However, it uses symbols with vague meaning instead of words. So, I'm stuck and asking for help.

Put simple: what do I write in place of ???.

line_length :: Integer -> Integer -> Integer -> Integer -> ???
line_length ax ay bx by =
            printf ("The length of the line between the points" ++
            "(%d,%d) and (%d,%d) is %.5f\n") ax ay bx by
            ((((fromIntegral (ax - bx)) ** 2.0) +
            ((fromIntegral (ay - by))) ** 2.0) ** 0.5)
Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
  • 7
    Printf is not a good start if you want to learn Haskell. What type would you like `line_length` to return? – augustss Feb 22 '12 at 10:37
  • 2
    BTW, `(^ 2)` is better than `(** 2)`, and `sqrt` is better than `(** 0.5)`. – augustss Feb 22 '12 at 10:44
  • 1
    The language reference certainly isn't a very good learning source for trying to understand the syntax. Have you not tried one of the easy books/tutorials, such as [LYAH](http://learnyouahaskell.com/), [RWH](http://book.realworldhaskell.org/) or [YAHT](http://www.cs.utah.edu/~hal/htut/)? – leftaroundabout Feb 22 '12 at 10:47
  • 1
    I would also like to quote from the documentation that you link to: "The return value is either String or (IO a)." – augustss Feb 22 '12 at 10:51
  • wvxvw, The answer is not straighforward because `printf` can return a string or it can print the string. It can do either depending on what you put instead of ???. – augustss Feb 22 '12 at 13:24
  • 2
    "I actually don't need to return anything from line_length"?? A function that doesn't need to return anything is completely meaningless in Haskell. – leftaroundabout Feb 22 '12 at 13:28
  • @leftroundabout: I think they mean that the return value isn't important since the side-effect (printing) is what matters. – amindfv Feb 22 '12 at 15:45
  • @wvxvw: The point is that if you chose `String` as the return type, it doesn't get printed. The only reason you see it in the REPL is because it automatically prints the value of expressions whose type are not `IO a`. When it comes to `IO ()`, leftroundabout's comment makes sense if you consider the IO action as the return value of the function, while your comment is talking about the result of running that action (which in the case of `printf` should indeed be ignored). – hammar Feb 22 '12 at 17:36

2 Answers2

12

The overloaded return type of printf :: PrintfType r => String -> r can be a little confusing, but it helps to look at the PrintfType instances which are available:

  1. First, we have this instance:

    instance (PrintfArg a, PrintfType r) => PrintfType (a -> r)
    

    This one is used to allow printf to take a variable number of arguments. This makes sense due to currying, but you probably won't think of it as the "true" return type of the function. For more information about how this works, see How does printf work in Haskell?.

  2. Then we have this instance,

    instance PrintfType (IO a)
    

    This means that we can use it as an IO action. Similar to print, this will print the result to standard output. The result of the action is undefined, so you should just ignore it.1

    > printf "Foo %d\n" 42 :: IO ()
    Foo 42
    > it
    *** Exception: Prelude.undefined
    
  3. And finally,

    instance IsChar c => PrintfType [c]
    

    The type class here is mostly to allow this to be Haskell 98 compliant. Since the only instance of IsChar is Char, you can think of this as

    instance PrintfType String
    

    but that would require the GHC-specific extension FlexibleInstances.

    What this instance means is that you can also just return a String without printing it, similar to sprintf in C.

    > printf "Foo %d\n" 42 :: String
    "Foo 42\n"
    

So depending on whether you want to print the result or just return it, you can replace ??? with either IO () or String in your example.

However, there is another problem with your code in that the compiler is unable to determine which type you wanted the last argument to be, so you have to help it out with a type annotation:

line_length :: Integer -> Integer -> Integer -> Integer -> String
line_length ax ay bx by =
            printf ("The length of the line between the points" ++
            "(%d,%d) and (%d,%d) is %.5f\n") ax ay bx by
            ((((fromIntegral (ax - bx)) ** 2.0) +
            ((fromIntegral (ay - by))) ** 2.0) ** 0.5 :: Double)

1 This is done to avoid extensions. With extensions, we could write instance PrintfType (IO ()) and the result would be ().

Community
  • 1
  • 1
hammar
  • 138,522
  • 17
  • 304
  • 385
1

When I get stuck with type signatures, I let Haskell do the hard work for me. That is, I

  • write the definition without a type signature
  • load it into ghci
  • interactively check the type that Haskell gave it
  • copy that signature down (making sure it's what I intended) in the source file
Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192