27
\$\begingroup\$

Edit 13 June:

I have made a new and improved version here.

Original Question

This is a very rudimentary lolcats translator (it only works on the phrase "Can I have a cheeseburger") in Haskell. This is my first ever attempt at Haskell (or any functional programming language). I am sure that my code is absolutely atrocious (the documentation was dismal) but it works.

Teh Codez

import Data.Strings
main = do
    putStrLn "Enter your text to translate"
    inputText <- getLine
    let capsText = strToUpper inputText
    let a = strReplace "HAVE" "HAS" capsText
    let b = strReplace "CAN I" "I CAN" a
    let c = strReplace "CHEESEBURGER" "CHEEZBURGER" b
    let d = strReplace " A " " " c
    putStrLn (d)
    getLine
\$\endgroup\$
  • 10
    \$\begingroup\$ @JamesFaix Mostly true, but Haskell's variables are not mutable as in other languages, so it's mainly a matter of idiomatic style, not safety. \$\endgroup\$ – 200_success Jun 8 '16 at 20:53
  • 1
    \$\begingroup\$ @Bergi The getLine was to keep the Windows console open after the output was printed. Any other ideas on how to do that? \$\endgroup\$ – Michael Brandon Morris Jun 8 '16 at 23:42
  • 3
    \$\begingroup\$ @MichaelBrandonMorris: I see. Usually you'd just run your program from the console, so it would stay open anyway. \$\endgroup\$ – Bergi Jun 9 '16 at 1:13
  • 1
    \$\begingroup\$ @Bergi Okay, but supposing someone runs it via the executable (double click, start menu, etc.) what are my options? \$\endgroup\$ – Michael Brandon Morris Jun 9 '16 at 10:59
  • 1
    \$\begingroup\$ In this particular case you could just recursively call main at the end of the main function so that your program repeatedly asks for input. Another way to accomplish this is forever from Control.Monad \$\endgroup\$ – BlackCap Jun 9 '16 at 12:01
37
\$\begingroup\$

It is good practice in Haskell to separate the functional code from the IO. In this case, you could (and therefore should) define a lolcat :: String -> String function. Be sure to put a type declaration on all functions — you didn't write one for your main.

Defining variables a, b, c, and d is overkill. I would write this as a composition of functions.

lolcat :: String -> String
lolcat = strReplace " A " " " .
         strReplace "CHEESEBURGER" "CHEEZBURGER" .
         strReplace "CAN I" "I CAN" .
         strReplace "HAVE" "HAS" .
         strToUpper
\$\endgroup\$
  • 1
    \$\begingroup\$ I think the difference between this code and the original code in the question does a great job of highlighting the difference between functional and imperative programming styles. A big thing that may be confusing about this is the point-free style: essentially, we are defining a function lolcat WITHOUT mentioning the argument of the function -- this might be hard to read at first, but essentially it's the same thing as saying lolcat(x) = strReplace " A " " " (strReplace "CHEESEBURGER" "CHEEZEBURGER" (... (strToUpper(x))...)) \$\endgroup\$ – vijrox Jun 9 '16 at 15:20
  • 1
    \$\begingroup\$ Point-free style can be confusing when learning functional programming for the first time coming from an imperative background, but it really is nice/preferable once you get used to it \$\endgroup\$ – vijrox Jun 9 '16 at 15:22
  • \$\begingroup\$ I think this is a very good demonstration of point-free style even at a beginner level. \$\endgroup\$ – immibis Jun 10 '16 at 6:25
22
\$\begingroup\$

I agree with 200_success and am just typing out the full program because you said you were a beginner.

import Data.Strings

encode :: String -> String
encode = strReplace " A " " "
       . strReplace "CHEESEBURGER" "CHEEZBURGER"
       . strReplace "CAN I" "I CAN"
       . strReplace "HAVE" "HAS"
       . strToUpper

main :: IO ()
main = do 
   putStrLn "Enter your text to translate"

   input <- getLine
   putStrLn $ encode input

The main function would perhaps more commonly be written as:

main :: IO ()
main = do
   putStrLn "Enter your text to translate"
   putStrLn =<< encode <$> getLine

EDIT:

@MichaelKlein asked me to explain (<$>) and (=<<), so I will do so as briefly as possible.

<$> is the inline version of fmap, which is the only function from the Functor typeclass. A typeclass is somewhat like a interface in an imperative language like Java.

class Functor f where
   fmap :: (a -> b) -> f a -> f b

fmap is used to run a function (a -> b) inside some data structure f a

fmap (+1) [1]      == [2]
fmap (+1) (Just 3) == Just 4
(+1) <$> (Just 3)  == Just 4

(<$>) :: f a -> ( a -> b ) -> f b

In Haskell, we treat the world as a datastructure IO. So just in the same way that you can run a function inside a list or a Maybe (like Just 3), you can run a function inside a IO.

encode             :: String -> String
getLine            :: IO String
encode <$> getLine :: IO String

(=<<) is part of the Monad typeclass, which is hard to explain briefly because you need to know about a few other typeclasses first.

(=<<) :: (a -> m b) -> m a -> m b

(=<<) is pronounced "bind", and is a flipped version of the more common (>>=). The important bit is that Haskell has a special syntax for working with monads: the do notation.

This:

main = do
  input <- getLine
  putStrLn input

Is syntatic sugar for this:

main = getLine >>= \input -> putStrLn input

Which is the same as this:

main = getLine >>= putStrLn


All of these are also equal:

main = do
   putStrLn "burger"
   input <- getLine
   putStrLn $ encode input

main = putStrLn "burger" 
    >> getLine >>= \input -> putStrLn $ encode input

main = putStrLn "burger" 
    >> encode <$> getLine >>= putStrLn

main = do 
  putStrLn "burger" 
  putStrLn =<< encode <$> getLine 
\$\endgroup\$
  • \$\begingroup\$ maybe add the type signatures too <$> :: f a -> ( a -> b ) -> f b and =<< :: (a -> m b) -> m a -> m b \$\endgroup\$ – Caleth Jun 9 '16 at 12:05
  • 1
    \$\begingroup\$ @BlackCap Thanks for adding some explanation. In a case like this, I think it's usually enough to explain what they mean just in the case where you used them here and mention that like show they do the same thing, but slightly differently for different types. +1 for going above and beyond. \$\endgroup\$ – Michael Klein Jun 9 '16 at 14:53
9
\$\begingroup\$

Remove repetition

You repeat a strReplace a lot:

        vvvvvvvvv
let a = strReplace "HAVE" "HAS" capsText
let b = strReplace "CAN I" "I CAN" a
let c = strReplace "CHEESEBURGER" "CHEEZBURGER" b
let d = strReplace " A " " " c
        ^^^^^^^^^^

What changes is what should be replaced by what: [("HAVE", "HAS"), ("CAN I", "I CAN"), ...]

Therefore your program should state the idea of replacement once and then contain this list of "replacement instructions".

To be more clear the function you should use has type String -> [(String, String)] -> String and example usage:

> replaceByTuples "foobar" [("foo", "fuu"),("bar", "baz")]
"fuubaz"

Using this function with your particular list of replacements will be easy.

This function is in this spoiler block. I advise you try to implement it by yourself.

import Data.List.Utils;


replaceByTuples s t = (foldl (.) id $ map (\(start, end) -> replace start end) t) s
More info and better implementations about this function to be found at: https://stackoverflow.com/questions/7862221/how-do-i-do-string-replace-in-haskell

\$\endgroup\$
  • 4
    \$\begingroup\$ I think you should swap the parameters of that replaceByTuples function, so that it is more convenient to use curried. Also you could quite trivially implement it by foldr (uncurry replace) :-) \$\endgroup\$ – Bergi Jun 9 '16 at 13:09
  • \$\begingroup\$ @Bergi Wow, I did not expect such a simple implementation. And yes. ) agree on swapping the arguments. \$\endgroup\$ – Caridorc Jun 9 '16 at 13:13
5
\$\begingroup\$

I agree with the other two answers, but I would use interact in main. This will make your program infinitely loop until there's no more input which may not be what you want, but it will solve the problem of keeping the console open. You'll have to define an eachLine function so that the program processes one line at a time as detailed in this stackoverflow answer.

As 200_success mentioned, it is good practice to write type-signatures for all top-level functions and main traditionally has type-signature: main :: IO (). Because of the getLine at the end of your main, it would have type-signature: main :: IO String, which would give your program the unintended side-effect of printing an additional string as it terminates. Writing the type-signatures explicitly allows the compiler to catch these sorts of problems.

This would be the full program:

import Data.Strings

main :: IO ()
main = do
    putStrLn "Enter your text to translate"
    interact (eachLine lolcat)

lolcat :: String -> String
lolcat = strReplace " A " " " .
         strReplace "CHEESEBURGER" "CHEEZBURGER" .
         strReplace "CAN I" "I CAN" .
         strReplace "HAVE" "HAS" .
         strToUpper

eachLine :: (String -> String) -> String -> String
eachLine f = unlines . map f . lines
\$\endgroup\$

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.