7
\$\begingroup\$

I started learning Haskell a couple of days ago and decided to build a Caesar shift in it.

import Data.Char
shift_str :: Int -> ([Char] -> [Char])
shift_str num
 | num > 1 = \str -> shift_str_forwards (shift_str (num-1) str)
 | num == 1 = shift_str_forwards
 | num == 0 = no_shift
 | num == -1 = shift_str_backwards
 | num < -1 = \str -> shift_str_backwards (shift_str (num+1) str)
no_shift :: [Char] -> [Char]
no_shift str = str
shift_str_forwards :: [Char] -> [Char]
shift_str_forwards str = map shift_forwards str
shift_str_backwards :: [Char] -> [Char]
shift_str_backwards str = map shift_backwards str
shift_forwards :: Char -> Char
shift_forwards char
 | char == 'z' = 'a'
 | otherwise = chr (1 + ord char)
shift_backwards :: Char -> Char
shift_backwards char
 | char == 'a' = 'z'
 | otherwise = chr (ord char - 1)

However, this seems far too complex to be the right way of doing it. Any advice?

Tolani
2,5017 gold badges31 silver badges49 bronze badges
asked Nov 28, 2016 at 8:51
\$\endgroup\$
0

2 Answers 2

6
\$\begingroup\$

First of all, it's great that you use type signatures. However, your using snake_case, whereas Haskell usually uses camelCase for functions, e.g. no_shift should be called noShift.

Next, your shift functions for characters can be simplified by both pattern matching and succ and pred:

shiftForwards :: Char -> Char
shiftForwards 'z' = 'a'
shiftForwards c = succ c
shiftBackwards :: Char -> Char
shiftBackwards 'a' = 'z'
shiftBackwards c = pred c

Next, your "global" function gets easier if you use str as an argument and not in a lambda.

shiftStr :: Int -> [Char] -> [Char]
shiftStr num str
 | num > 0 = shiftStr (num - 1) (map shiftForwards str)
 | num < 0 = shiftStr (num + 1) (map shiftBackwards str)
 | num == 0 = str

The special cases for num == 1 and num == -1 aren't necessary, since shiftStr will return immediately if the subsequent call uses num = 0.

ferada
11.4k25 silver badges65 bronze badges
answered Nov 28, 2016 at 9:12
\$\endgroup\$
0
6
\$\begingroup\$

shiftStr 'encrypts' each letter of input string independently of other letters. This could be emphasized by using map at the top of shiftStr instead of hiding it in helper functions:

shiftStr :: Int -> String -> String
shiftStr n xs = map (shiftChar n) xs

Or in pointfree style without variables:

shiftStr :: Int -> String -> String
shiftStr = map . shiftChar

shiftChar n c replaces letter by searching for another one n positions away. I'll implement this literally by creating cycled alphabet and searching for letters in it. This is inefficient but allows to get taste of laziness.

alphabet = ['a'..'z'] :: String
alphaLoop = cycle alphabet :: String
shiftChar :: Int -> Char -> Char
shiftChar n c = head $ drop n $ dropWhile (/= c) alphaLoop

cycle creates cycle from a list. dropWhile skips some characters and returns list starting from c.

Try it in ghci (alphaLoop is infinite so be careful with it).

> let alphaLoop = cycle ['a'..'z']
> take 30 alphaLoop
"abcdefghijklmnopqrstuvwxyzabcd"
> take 30 $ dropWhile (/= 'x') alphaLoop
"xyzabcdefghijklmnopqrstuvwxyza"
> take 30 $ drop 1 $ dropWhile (/= 'x') alphaLoop
"yzabcdefghijklmnopqrstuvwxyzab"

alphaLoop is a loop, so we can skip length alphabet - 10 letters instead of moving 10 letters backwards:

shiftChar :: Int -> Char -> Char
shiftChar n c = head $ drop (length alpha + n) $ dropWhile (/= c) alphaLoop

Full code is like this:

shiftStr :: Int -> String -> String
shiftStr n = map shiftChar
 where
 alphabet = ['a'..'z']
 alphaLoop = cycle alphabet
 shiftChar c = head
 $ drop (length alphabet + n)
 $ dropWhile (/= c) alphaLoop

Now try shiftStr 13 "ABC" and see if you can explain its behavior.

answered Nov 28, 2016 at 16:59
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.