Brute force Caesar Cipher decrypter
import Data.Char (chr, isPunctuation, isSymbol, ord)

decUpper = 127 --Upperbound of normal characters
decLower = 32 --Lowerbound of normal characters
decRange = decUpper - decLower --Total range

type Message = String

-- | Checks if the char is in range
isInRange :: Char -&gt; Bool
isInRange char = ichar &lt;= decUpper &amp;&amp; ichar &gt;= decLower
    where ichar = ord char

-- Test if all chars in the Message are in range
isPossible :: Message -&gt; Bool
isPossible = all isInRange

-- Turn a list into a list of pairs
mkPairs :: [a] -&gt; [(a,a)]
mkPairs xs = zip xs (tail xs)

-- no consecutive symbols in Message
isProbable :: Message -&gt; Bool
isProbable = not . any (\(x,y) -&gt; isSym x &amp;&amp; isSym y) . mkPairs
  where isSym c = isSymbol c || isPunctuation c

-- Shifts all characters by n
decShift :: Message -&gt; Int -&gt; Message
decShift []     _  = []
decShift (x:xs) n
    | iNewChar &gt; 0 = chr iNewChar : decShift xs n
    | otherwise    = x : decShift xs n
  where iNewChar = ord x + n

-- Returns a list of tuples of the offset used, and the shifted Message
getCombos :: Message -&gt; [(Int,Message)]
getCombos m = [(off,decShift m off) | off &lt;- [negate decRange .. decRange]]

-- Returns a list of tuples of possible decryptions (KeyUsed,Message)
simpDec :: Message -&gt; [(Int, Message)]
simpDec = filter ((\x -&gt; isPossible x &amp;&amp; isProbable x) . snd) . getCombos


Changes:


used more function composition
simpDec is really a filter on results. getCombos generates data so a list comprehension makes sense.
simplify isProbable with the definition of mkPairs
simplify isPossible using the all function.
added import line