Contrary to Church's lambda calculus, Haskell is typed.
When types are denoted with lower-case letters (such as
p2) that indicates a type variable. When types are denoted with capital letters (e.g.
Int) they're concrete types.
p1 -> p2 -> p2 indicates a function in curried form. You can think of it as a function that takes a
p1 value as input and returns a new function of the type
p2 -> p2, or you can think of it as a function that takes
p2 as inputs, and returns
p2 as output. The right-most type indicates the return type.
p2 are here unconstrained, which means that they can be literally any type -
Int, or your own custom type(s). They can also be equal. For example, both
p2 could be
The interpretation of
p1 -> p2 -> p2 as a function that takes
p1 as input and returns a function with the type
p2 -> p2 makes most sense. What really happens is that in the expression
\x -> \x -> x the second
x shadows the first
x. If you think of the expression as
\x -> (\x -> x), you can see that the returned function is
\x -> x, and that it doesn't matter what the 'leftmost'
x was. Thus, the type of the leftmost
x is irrelevant because the value is never used. It'd be more idiomatic to write the expression as
\_ -> \x -> x. The underscore is Haskell's wildcard placeholder, indicating that there's a variable there, but we don't care about it.
In the expression
\x -> x, however, the input value is being returned unmodified, so that the type must be
p2 -> p2 - i.e. the output type must be the same as the input type.
\x -> x is the identity function, and it's already built into Haskell as the function
id. Likewise, a function that ignores its input and instead always returns the same result is called
const. Thus, the expression
\x -> \x -> x is equivalent to
\x x -> x is invalid in Haskell. It would indicate a (curried) function that takes two arguments, but returns a value. Which one of them? Keep in mind that they could be different.
In Haskell, variables are bound to input arguments, but the binding must be unambiguous. That's the way that the language works, but it's quite a standard design.
For an example of a language that behaves differently you might look to Erlang, where a function that receives two arguments with the same variable name indicates a pattern-match where the match only succeeds if the arguments are equal to each other. Erlang is the only language I'm aware of that does it that way, but I only know a handful of languages...