Haskell: Where vs. Let

Solution 1:

1: The problem in the example

f :: State s a
f = State $ \x -> y
    where y = ... x ...

is the parameter x. Things in the where clause can refer only to the parameters of the function f (there are none) and things in outer scopes.

2: To use a where in the first example, you can introduce a second named function that takes the x as a parameter, like this:

f = State f'
f' x = y
    where y = ... x ...

or like this:

f = State f'
    where
    f' x = y
        where y = ... x ...

3: Here is a complete example without the ...'s:

module StateExample where

data State a s = State (s -> (a, s))

f1 :: State Int (Int, Int)
f1 = State $ \state@(a, b) ->
    let
        hypot = a^2 + b^2
        result = (hypot, state)
    in result

f2 :: State Int (Int, Int)
f2 = State f
    where
    f state@(a, b) = result
        where
        hypot = a^2 + b^2
        result = (hypot, state)

4: When to use let or where is a matter of taste. I use let to emphasize a computation (by moving it to the front) and where to emphasize the program flow (by moving the computation to the back).

Solution 2:

While there is the technical difference with respect to guards that ephemient pointed out, there is also a conceptual difference in whether you want to put the main formula upfront with extra variables defined below (where) or whether you want to define everything upfront and put the formula below (let). Each style has a different emphasis and you see both used in math papers, textbooks, etc. Generally, variables that are sufficiently unintuitive that the formula doesn't make sense without them should be defined above; variables that are intuitive due to context or their names should be defined below. For example, in ephemient's hasVowel example, the meaning of vowels is obvious and so it need not be defined above its usage (disregarding the fact that let wouldn't work due to the guard).

Solution 3:

Legal:

main = print (1 + (let i = 10 in 2 * i + 1))

Not legal:

main = print (1 + (2 * i + 1 where i = 10))

Legal:

hasVowel [] = False
hasVowel (x:xs)
  | x `elem` vowels = True
  | otherwise = False
  where vowels = "AEIOUaeiou"

Not legal: (unlike ML)

let vowels = "AEIOUaeiou"
in hasVowel = ...