Search code examples
haskelliopalindrome

Haskell: How to read user input to be used in a function inside of a do block?


I am attempting to run a simple palindrome program in Haskell but I want to request input from the user to be used in the program.

code:

palindrome :: String -> Bool
palindrome x = x == reverse x
main = do
  putStrLn "Brendon Bailey CSCI 4200-DB"
  putStrLn "Enter word to test for Palindrome"
  palindrome <- getLine
  putStrLn "Thanks"

The code works but it just displays "thanks". If I exclude the last line of the block I get the "last line of do block must be expression" and if I include the actual palindrome code above the do block I get errors saying I need a "let" statement but incorporating one causes more errors. I am brand new to Haskell (and functional programming in general. I do java, c++, and web langs) I think the explanation here will be simple but after reading input output documentation on haskell I still can't figure out the proper way to accomplish this.

I'm doing it this way because my assignment stipulates that I must show the class info when the program is run. The palindrome part works fine without the do block. I'm just attempting to automatically output my text before testing a palindrome. Also how can I set up a haskell script to just run when loaded? I don't want the user to have to type "main" I want it to just run.

edit:

palindrome :: String -> Bool
palindrome x = x == reverse x
main = do
  putStrLn "Brendon Bailey CSCI 4200-DB"
  putStrLn "Enter Word"
  x <- getLine
  palindrome x
  putStrLn "thanks"

This also doesn't work. How do I get the users input to use in the palindrome???

If the code:

palindrome :: String -> Bool
palindrome x = x == reverse x

works when "palindrome "etc"" is entered in GHCi then why doesn't my second code version do the same thing???

edit 2: Don't forget to enclose print statements in parentheses or Haskell will think it separate input is a separate argument. Dumb mistake but easy to make.


Solution

  • This question is multiple questions in one, so I'll try to address each problem. I will address these in a different order to which they were stated in.

    Before we begin, it must be said that you have critical misconceptions about the way Haskell works. I strongly recommend reading Learn You a Haskell for Great Good, which may seem juvenile, but is in fact a stellar introduction to functional programming, and in fact is the way many of us began learning Haskell.

    Question 1: Compilation, GHC, and Haskell executables

    (...) how can I set up a haskell script to just run when loaded? I don't want the user to have to type "main" I want it to just run.

    You are confusing interpreted scripting languages like Python, Javascript, or Ruby, with compiled languages like C, Java, or Haskell.

    Programs in interpreted languages are run by a program on the computer that contains information on how to interpret the text written, hence the name 'interpreted'. However, compiled languages are written, then converted from text into machine code, which is almost unreadable by humans, but which computers can run quickly.

    When we write a complete, executable program in Haskell, we expect to compile it with The Glasgow Haskell Compiler, GHC (not GHCi). This looks something like this:

    $ cat MyProgram.hs
    main :: IO ()
    main = putStrLn "This is my program!"
    $ ghc MyProgram.hs
    [1 of 1] Compiling Main             ( MyProgram.hs, MyProgram.o )
    Linking MyProgram ...
    $ ./MyProgram
    This is my program!
    

    Now, MyProgram.hs was converted by GHC from a Haskell source code file into an executable, MyProgram. We defined an IO action, called main, to tell the compiler where we wanted it to begin. This is called an entry point, and is necessary to create a standalone executable. This is how Haskell programs are created and run.

    Crucially, GHCi is not how Haskell programs are run. GHCi is an interactive environment for experimenting with Haskell functions and definitions. GHCi allows you to evaluate expressions (such as palindrome "Hello", or main) but is not intended as the way to run Haskell programs, in the same way that python's IDLE is for Python programs.

    Question 2: Functions vs IO actions

    (...) If you just have the palindrome code outside of the do block and the user enters "palindrome "etc"" in GHCi the program returns the boolean as output (...)

    Haskell does not behave like languages you may be used to. Functions in Haskell are not programs. When you defined your function palindrome, you made a way for the computer to convert a String into a Bool. You did not tell it how to output that Bool in any way. Hence, there is an extra step: the IO monad.

    Haskell programs are, in a way, represented as data, hence the somewhat strange type signature main :: IO (). I will not be explaining monads in detail here, but put simply, in a do-block, you can only state things of type IO a where a is any type. So, when you wrote:

    main = do
        -- (...)
        palindrome x
        -- (...)
    

    It didn't make sense. Think of it this way: how can you 'run' or 'execute' a Bool? It's a boolean, not a program!

    However, there is a function, called print, which allows you to do exactly that. Your line should be:

    main = do
        -- (...)
        print (palindrome x)
        -- (...)
    

    I won't go into prints type signature, but rest assured that it does indeed return an IO (), which allows us to put it into a do-block. By the way, if you write:

    palindrome :: String -> Bool
    palindrome x = x == reverse x
    
    main = do
      putStrLn "Brendon Bailey CSCI 4200-DB"
      putStrLn "Enter Word"
      x <- getLine
      print (palindrome x)
      putStrLn "thanks"
    

    ...and compile the file with GHC, you will get the desired effect, assuming you did in fact want the program to output True or False. In fact, I compiled this as Palindromes.hs, and here's the result:

    $ ghc Palindromes.hs 
    [1 of 1] Compiling Main             ( Palindromes.hs, Palindromes.o )
    Linking Palindromes ...
    $ ./Palindromes
    Brendon Bailey CSCI 4200-DB
    Enter Word
    amanaplanacanalpanama
    True
    thanks
    

    Question 3: GHCi as a testing environment

    [My code] works when "palindrome "etc"" is entered in GHCi then why doesn't my second code version do the same thing???

    GHCi, as stated before, is different from GHC, in that it is an environment to test and 'play' with Haskell code before writing standalone programs.

    In GHCi, you write expressions, and they are evaluated (using GHC's machinery), the result is printed, and it loops, Hence, this kind of system is often called a Read-Evaluate-Print-Loop, or a REPL.

    Let's examine a bit of code in GHCi:

    λ let plusOne n = n + 1
    λ plusOne 5
    6
    

    The first line defines a value (here a function) that we can use later. The second line, however, is not a definition, but an expression. Hence, GHCi evaluates it, and prints the result.

    GHCi can also execute IO actions:

    λ let myProgram = putStrLn "Hello!"
    λ myProgram
    Hello!
    

    This works because evaluating an IO action in Haskell is equivalent to executing them: this is how Haskell is designed to work - it's a truly brilliant idea provided that you grasp it.

    Even though we see these similarities, GHCi is not equivalent to a Haskell program, as one might expect after using Python. GHCi unfortunately bears little resemblance to 'real' Haskell code, because Haskell works in a fundamentally different way to most other languages.

    GHCi is nothing more than a useful way to quickly see results of our code, as well as other useful information that I won't explain here.

    Other points

    Monads are a key part of the problem here. There have been many, many questions on Monads here on StackOverflow as they tend to be a sticking point, so any questions you may have are likely here. This makes things difficult, since Haskell IO cannot be fully appreciated without an understanding of Monads. There is an LYAH chapter on Monads.