[Haskell-beginners] types, parentheses, application, composition

Daniel Fischer daniel.is.fischer at googlemail.com
Sun Nov 25 14:43:47 CET 2012


On Sonntag, 25. November 2012, 02:27:31, Christopher Howard wrote:
> Could someone explain more precisely to me what is the significance of
> parentheses in the type of an expression? Are they just some kind of
> syntactic sugar, that simplifies into a type without them, or are they
> more like parentheses in algebra, or...?

More like parentheses in algebra, they serve to group together parts of a 
(type) expression that would be grouped differently by the precedence and/or 
associativity of operators, e.g. in

foo :: (Int -> a) -> a
foo f = f 42

The function arrow associates to the right, so without them, the signature 
would become  Int -> (a -> a) which is an entirely different type.

Or

bar :: Either a (b -> a) -> b -> a

where it's precedence. Prefix type application binds strongest (like function 
application at the value level), so we can't omit the parentheses, otherwise 
we'd get

(Either a b) -> a -> b -> a

> 
> In particular, I'm having difficulty understanding the results of
> composition or application that involves parentheses... I think I've got
> it figured out and then I find another strange expression that doesn't
> have the type I expect. Here is an example using the (self-made)
> function "sqr":
> 
> code:
> --------
> 
> > :t sqr
> 
> sqr :: Double -> Double
> 
> > :t (.)
> 
> (.) :: (b -> c) -> (a -> b) -> a -> c

Let's be more verbose and call it

(.) :: (intermediate -> final) -> (original -> intermediate)
          -> original -> final

> 
> > :t (. sqr)

The section (. sqr) is equivalent to \f -> f . sqr, so sqr is the second 
argument of (.) and we must unify its type with the type of the second 
argument of (.), which is (original -> intermediate).

So original = Double, intermediate = Double, and here (.) is used at the more 
restricted type

(.) :: (Double -> final) -> (Double -> Double) -> Double -> final

the second argument is supplied, hence its type is removed, leaving

(. sqr) :: (Double -> final) -> Double -> final

> 
> (. sqr) :: (Double -> c) -> Double -> c

Yup, modulo renaming, we have exactly that.

> 
> > :t ((. sqr) .)

Now, (. sqr) is used as the first argument of (.). So we must unify its type 
with (intermediate -> final), the type of (.)'s first argument.

Now, the function arrow is right-associative, so we can also write

(. sqr) :: (Double -> c) -> (Double -> c)

and that makes it clear that

intermediate = (Double -> c)
final = (Double -> c)

So the outer (.) is used at type

(.) :: ((Double -> c) -> (Double -> c)) -> (original -> (Double -> c)) -> 
original -> (Double -> c)

The first argument is supplied, so

((. sqr) .) :: (original -> (Double -> c)) -> original -> (Double -> c)

> 
> ((. sqr) .) :: (a -> Double -> c) -> a -> Double -> c

Yup, modulo renaming and parentheses that can be omitted because -> is right-
associative, that is exactly the same.

It's just easier to see what corresponds to what with the parentheses.

> --------
> 
> Everything makes sense up until the last expression, "((. sqr) .)".
> Where did the "(a -> Double -> c)" come from? What's going on here?

The pretty-printer of type signatures in ghci omits unnecessary parentheses. 
Read it (a -> (Double -> c)).

While for small types like here, unnecessary parentheses can increase the 
readability, for larger types, they usually decrease readability much more 
(ever looked at Lisp code?), because all is drowned in parentheses.




More information about the Beginners mailing list