Why is it so uncommon to use type signatures in where clauses?

2.4k views Asked by At

Does it help the compiler to optimise, or is it just surplus work to add additional type signatures? For example, one often sees:

foo :: a -> b
foo x = bar x
      where bar x = undefined

Rather than:

foo :: a -> b
foo x = bar x
      where bar :: a -> b
            bar x = undefined

If I omit the top level type signature, GHC gives me a warning, so if I don't get warnings I am quite confident my program is correct. But no warnings are issued if I omit the signature in a where clause.

4

There are 4 answers

0
Ben On BEST ANSWER

Often definitions in where clauses are to avoid repeating yourself if a sub-expression occurs more than once in a definition. In such a case, the programmer thinks of the local definition as a simple stand-in for writing out the inline sub-expressions. You usually wouldn't explicitly type the inline sub-expressions, so you don't type the where definition either. If you're doing it to save on typing, then the type declaration would kill all your savings.

It seems quite common to introduce where to learners of Haskell with examples of that form, so they go on thinking that "normal style" is to not give type declarations for local definitions. At least, that was my experience learning Haskell. I've since found that many of my functions that are complicated enough to need a where block become rather inscrutable if I don't know the type of the local definitions, so I try to err towards always typing them now; even if I think the type is obvious while I'm writing the code, it may not be so obvious when I'm reading it after not having looked at it for a while. A little effort for my fingers is almost always outweighed by even one or two instances of having to run type inference in my head!

Ingo's answer gives a good reason for deliberately not giving a type to a local definition, but I suspect the main reason is that many programmers have assimilated the rule of thumb that type declarations be provided for top level definitions but not for local definitions from the way they learned Haskell.

2
gfour On

Adding a type signature can make your code faster. Take for example the following program (Fibonacci):

result = fib 25 ;
-- fib :: Int -> Int
fib x = if x<2 then 1 else (fib (x-1)) + (fib (x-2))
  • Without the annotation in the 2nd line, it takes 0.010 sec. to run.
  • With the Int -> Int annotation, it takes 0.002 sec.

This happens because if you don't say anything about fib, it is going to be typed as fib :: (Num a, Num a1, Ord a) => a -> a1, which means that during runtime, extra data structures ("dictionaries") will have to be passed between functions to represent the Num/Ord typeclasses.

0
Don Stewart On

Often where declarations are used for short, local things, which have simple types, or types that are easily inferred. As a result there's no benefit to the human or compiler to add the type.

If the type is complex, or cannot be inferred, then you might want to add the type.

While giving monomorphic type signatures can make top level functions faster, it isn't so much of a win for local definitions in where clauses, since GHC will inline and optimize away the definitions in most cases anyway.

1
Ingo On

There exists a class of local functions whose types cannot be written in Haskell (without using fancy GHC extensions, that is). For example:

f :: a -> (a, Int)
f h = g 1
  where g n = (h, n)

This is because while the a in the f type signature is polymorphic viewed from outside f, this is not so from within f. In g, it is just some unknown type, but not any type, and (standard) Haskell cannot express "the same type as the first argument of the function this one is defined in" in its type language.