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:
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?.
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
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 ()
.