3
\$\begingroup\$

KITTEH HAS IMPROOVD CODE SKILLS, AN NAO HAS BETTR CODE 4 U

This is an improvement of my original Haskell Lolcats Translator, and now features a function to do the translation (recursively?) using a map. I still have two let statements, which I know are frowned upon, but I couldn't get both the strToUpper and inline fmap working, so I opted for strToUpper.

Sample IO:

Enter your text to translate:
Let me have some cake on a plate please
LEMME HAS SUM CAEK ON PLAET PLZ

Teh Codez

import Data.Map
import Data.Strings

type Dictionary = [(String, String)]
lolcatDictionary =
    [
        (" A ", " "),
        ("CAN I", "I CAN"),
        ("MORE", "MOAR"),
        ("CHEESEBURGER", "CHEEZBURGER"),
        ("HAVE", "HAS"),
        ("HI", "HAI"),
        ("GHOST", "GOAST"),
        ("FEET", "FEAT"),
        ("MOAN", "MOWN"),
        ("CROWD", "CROUD"),
        ("NOTHING", "NUTHING"),
        ("MY", "MAI"),
        ("THEM", "DEM"),
        ("THESE", "THEES"),
        ("YOU", "YU"),
        ("LET ME", "LEMME"),
        ("KITE", "KIET"),
        ("CAT", "KAT"),
        ("CATS", "KATS"),
        ("KITTEN", "KITTEH"),
        ("KITTY", "KITTEH"),
        ("KITTENS", "KITTEHS"),
        ("LIKE", "LIEK"),
        ("COME", "COEM"),
        ("CAME", "CAEM"),
        ("BAKE", "BAEK"),
        ("PLATE", "PLAET"),
        ("SOME", "SUM"),
        ("CAKE", "CAEK"),
        ("PLEASE", "PLZ")
    ]

main :: IO ()
main = do
    putStrLn "Enter your text to translate:"
    input <- getLine
    let intermediate = strToUpper input
    let output = stringMapReplace lolcatDictionary intermediate
    putStrLn output

stringMapReplace :: Dictionary -> String -> String
stringMapReplace [] string = string
stringMapReplace (m:ms) s = stringMapReplace ms (strReplace (fst m) (snd m) s)
\$\endgroup\$
2
\$\begingroup\$

What is the Data.Map doing there? It looks like you were going to use it, but then changed your mind and used a list of pairs instead.

The stringMapReplace function is doing explicit recursion. A more advanced Haskeller would recognize this pattern as some kind of fold:

-- if the list is empty, the result is the initial value z; else
-- apply f to the first element and the result of folding the rest
foldr f z []     = z 
foldr f z (x:xs) = f x (foldr f z xs) 

-- if the list is empty, the result is the initial value; else
-- we recurse immediately, making the new initial value the result
-- of combining the old initial value with the first element.
foldl f z []     = z                  
foldl f z (x:xs) = foldl f (f z x) xs

In this case, the function can be tersely written as

stringMapReplace :: Dictionary -> String -> String
stringMapReplace = flip $ foldr $ uncurry strReplace

Intermediate steps to get there

First, recognize that your dictionary is the thing that is being iterated through. (That is, your (m:ms) plays the role of the (x:xs) in the fold.) In Haskell, it is usually more convenient to place the parameter that varies the most at the end. So, rewrite your original

stringMapReplace (m:ms) s = stringMapReplace ms (strReplace (fst m) (snd m) s)

as

stringMapReplace :: Dictionary -> String -> String
stringMapReplace map s = stringMapReplace' s map
  where
    stringMapReplace' s [] = s
    stringMapReplace' s (m:ms) = stringMapReplace' (strReplace (fst m) (snd m) s) ms

Now, we can see that stringMapReplace' fits the pattern for foldl. However, foldr is preferable to foldl. If we perform the substitutions in a different order…

stringMapReplace :: Dictionary -> String -> String
stringMapReplace map s = stringMapReplace' s map
  where
    stringMapReplace' s [] = s
    stringMapReplace' s (m:ms) = strReplace (fst m) (snd m) (stringMapReplace' s ms)

… we can make stringMapReplace' fit the pattern for foldr.

stringMapReplace map s = stringMapReplace' s map
  where
    stringMapReplace' s map = foldr f s map
    f m = strReplace (fst m) (snd m)

Notice that in stringMapReplace' s map = foldr f s map, the s map is repeated on both sides. We can write that in point-free style as stringMapReplace' = foldr f.

But what is f? We can simplify it as uncurry strReplace.

stringMapReplace map s = stringMapReplace' s map
  where
    stringMapReplace' = foldr $ uncurry strReplace

What is the relationship between stringMapReplace and stringMapReplace'? The parameters are flipped.

stringMapReplace = flip $ foldr $ uncurry strReplace

QED.

\$\endgroup\$
1
\$\begingroup\$

You can get rid of the lets and even the binding of getLine to input in main.

First, let's identify what exactly you're doing to the input before printing it. First you call strToUpper on it and then pass the result to stringMapReplace lolcatDictionary, using function composition, we can easily make this into one function:

stringMapReplace lolcatDictionary . strToUpper

What we have now is a single String -> String function and a IO String (getLine) value. We could just unbox the string from the IO action and then apply the function to it like this:

input <- getLine
putStrLn . stringMapReplace lolcatDictionary . strToUpper $ input

Or we can one line everything, using fmap and >>=

fmap (stringMapReplace lolcatDictionary . strToUpper) getLine >>= putStrLn

fmap takes a function that transforms values and a value that's in a Functor (IO in this case) and transforms the value in that Functor preserving the context. Or put another way, fmap takes a function that transforms a value and returns a function that transforms values contained in a Functor.

>>= takes a value inside a Monad (also IO) and a function that takes a value and returns a value in a Monad and returns a value in a Monad. In our case, the result of the fmap is like a String in an IO box. Using >>= we can pass that box onto putStrLn, which expects just a String and returns an IO action. We need to return an IO action since we are in a do.

We can write this more nicely though, there's a built-in function called interact. It takes a String -> String function and returns an empty IO action, perfect. We just need to remember to use lines and unlines, so we can display a result after each newline is entered and we're golden:

interact $ unlines . map (stringMapReplace lolcatDictionary . strToUpper) . lines

It is important to note that this version is not functionally equivalent to the previous one. The previous versions read one line and then exited, this one reads line per line until it encounters an EOF.

Putting this together along with 200_success' implementation of stringMapReplace, that gives us:

import Data.Strings (strReplace, strToUpper)

type Dictionary = [(String, String)]
lolcatDictionary =
    [
        (" A ", " "),
        ("CAN I", "I CAN"),
        ("CHEESEBURGER", "CHEEZBURGER"),
        ("HAVE", "HAS"),
        ("MORE", "MOAR"),
        ....
    ]

main :: IO ()
main = do
    putStrLn "Enter your text to translate:"
    interact $ unlines . map (stringMapReplace lolcatDictionary . strToUpper) . lines

stringMapReplace :: Dictionary -> String -> String
stringMapReplace = flip $ foldr $ uncurry strReplace

You seem to be diving into the deep end of Haskell, without learning about the language first. There are some languages where this is a good idea, I'm afraiid Haskell is not one of them. If you really are interested in Haskell, I'd suggest you pick up a book about Haskell first and see how things are done in it and why they are done that way before writing too much code that deals with IO.

\$\endgroup\$
  • 1
    \$\begingroup\$ Why wrap f in unlines . map f . lines? Shouldn't those String transformations be streaming? \$\endgroup\$ – Gurkenglas Jun 14 '16 at 16:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.