22

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.

ehird
  • 40,602
  • 3
  • 180
  • 182
epsilonhalbe
  • 15,637
  • 5
  • 46
  • 74
  • I can't answer the question of whether or not it will make compiling faster, but it's always good practice to write out type signatures if it's a non-trivial function. – Wes May 15 '12 at 22:56
  • 7
    Also, many of these local definitions need to access type variables from outer scope, which tends to clutter the code with `asTypeOf` and friends, unless you use `ScopedTypeVariables`. – Vitus May 15 '12 at 23:06
  • 1
    If your function is important enough to get a type declaration, why not make it a first class citizen? – rotskoff May 15 '12 at 23:50
  • 1
    @roskoff Who says it's only "important" things that need type declarations? Sometimes sub-expressions are complex or repeated enough in the definition of a top-level function that it's worth factoring them out and giving them a name to make it easier to read the the top-level definition, but they can still far too tied to the internals of the main definition to want to split them off into another top-level definition. In that case, it can help to give the where definition a type declaration if it makes it easier to read (which is rather often). – Ben May 16 '12 at 00:36

4 Answers4

22

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.

Ingo
  • 36,037
  • 5
  • 53
  • 100
  • 7
    Though `{-# LANGUAGE ScopedTypeVariables #-}` does allow you to give the type `g :: Int -> (a, Int)` if you modify the type signature for `f` to be `forall a. a -> (a, Int)`. – Dan Burton May 16 '12 at 01:10
20

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.

Ben
  • 68,572
  • 20
  • 126
  • 174
11

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.

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
0

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.

gfour
  • 959
  • 6
  • 9
  • 2
    The question asks about type signatures of local definitions. It's actually very common to give type signature to top-level definitions. – Vitus May 15 '12 at 23:09
  • 2
    I agree with Vitus, this answer is less relevant to local definitions. It's very common to write top-level definitions that without a type signature get assigned more general types than you might have needed and therefore run slower. Local definitions are almost always constrained by the type signature of the top level definition in which they occur, so this problem doesn't arise nearly so often if you're typing your top-level definitions. Even if the local definition is not formally constrained, the compiler knows it's local and doesn't need to be any more general than its local use. – Ben May 16 '12 at 00:48