Beauty and the Strings in Haskell
If you're going to use a list in Haskell, the last thing you want to do is continually modify random elements within it. Luckily, there's a better way.

Start by listing out all of the different things your function is doing.


Drop characters that are not 'a'..'z' or 'A'..'Z'.
Regardless of case, determine a letter's position in the alphabet.
Tally a value that represents a specific letter in the alphabet.
Return a list of alphabet letter frequencies.


By identifying the smallest bits of functionality that still make sense to talk about, we can write or reuse functions that do only one thing, and then compose them using higher order functions into something that does everything we need in one go.

Since this problem is dependent on characters and lists, your first stop as a new Haskell user should be to the Haddock documentation for Data.Char and Data.List to learn about what pre-existing functions you might be able to use for your solutions.

Tackling the first bullet point, dropping some elements of a list conditionally on their values should jump out at you as a filter operation. If we look through the documentation for Data.Char you may realize that Char represents all Unicode characters&mdash;not just ASCII values&mdash;but we're only interested in the ASCII letters.

Knowing that Char is an instance of Enum (from the Data.Char documentation), we can use list range notation to get just the Char values we care about in a list. I.e., alphabet = ['a'..'z']. Instead of expanding our range to include the uppercase versions of letters, we'll next write a function to lowercase our string using toLower :: Char -&gt; Char from Data.Char.

lower :: String -&gt; String
lower = map toLower


And assuming we have a lowercase string, it's easy to filter to just the characters we're interested in.

alphas :: String -&gt; Char
alphas = filter (`elem` alphabet)


Notice that we're building functions that can be easily composed to produce the functionality our final program requires. Going from an arbitrary string to just one containing valid letters all in lower case is easy, alphas . lower.

Going by the original statement of functionality, we next need to determine a letter's position in the alphabet so that we can then keep a tally of letter frequencies. In writing a functional version though we can be smarter. Instead of picking a letter and then finding the correct tally to increase, we rearrange the order of operations. First we group letters and then we count the number of elements in each group to produce a tally.

Perusing the documentation for Data.List we'll pull out sort and group. sort will rearrange our list of Char, then group will organize them into lists by equality. The last step is to run length encode the groups to produce a tally for each letter.

tally :: (Ord a) =&gt; [a] -&gt; [(a, Int)]
tally = map runLength . group . sort
    where
        runLength :: [a] -&gt; (a, Int)
        runLength       [] = error "runLength: Can't run length encode an empty list"
        runLength xs@(x:_) = (x, length xs)


That error case should be unnecessary, group shouldn't return any empty sub-lists, but we include it so that if our understanding is incorrect or future changes to the function ever break things we get a better error than a pattern-match failure (and the compiler stops complaining if we compile with -Wall).

Now one thing to note is that if a letter isn't present at all in a given string, tally won't return a pair for it. In your original computeMaxBeauty that's a problem since it relies on a list of length 26 being returned. Since we have alphabet which encodes letter value by position though we can write another version that is more flexible.

beauty :: (Char, Int) -&gt; Int
beauty (a, n) = n * (fromMaybe 0 (elemIndex a alphabet) + 1)


Here we look up the index of a Char in alphabet, then use a utility function from Data.Maybe in the event that Char wasn't present. Because lists are 0-indexed we have to add 1.

And tying all of our simple single-purpose functions together,

computeMaxBeauty :: String -&gt; Int
computeMaxBeauty = sum . map beauty . tally . alphas . lower




My working version, should be runnable.

import Data.Char (toLower)
import Data.List (group, sort)
import Data.Maybe (fromMaybe)

alphabet :: [Char]
alphabet = ['a'..'z']

lower :: String -&gt; String
lower = map toLower

alphas :: String -&gt; [Char]
alphas = filter (`elem` alphabet)

tally :: [Char] -&gt; [(Char, Int)]
tally = map runLength . group . sort
    where
        runLength :: [a] -&gt; (a, Int)
        runLength       [] = error "runLength: Can't run length encode an empty list"
        runLength xs@(x:_) = (x, length xs)

-- Matches original functionality, unnecessary with changed computeMaxBeauty as below
letterFreqs :: String -&gt; [Int]
letterFreqs = map ((subtract 1) . snd) . tally . (alphabet ++) . alphas . lower

computeMaxBeauty :: String -&gt; Int
computeMaxBeauty = sum . map beauty . tally . alphas . lower
    where
        beauty :: (Char, Int) -&gt; Int
        beauty (a, n) = n * (fromMaybe 0 (elemIndex a alphabet) + 1)