tags:

views:

219

answers:

3

Hello,

I would like to know what is the best solution to create simple menu with functionality described below (pseudo code) just like im used to:

while (true){ x = readLine(); case (x): x == "1" then do sth1 function x == "2" then do sth2 function }

thanks for help, maybe any other ideas on how to make menu not in pattern described above?

+4  A: 

Something like

menu :: IO ()
menu = do
      putStrLn . unlines $ map concatNums choices
      choice <- getLine
      case validate choice of
         Just n  -> execute . read $ choice
         Nothing -> putStrLn "Please try again"

      menu
   where concatNums (i, (s, _)) = show i ++ ".) " ++ s

validate :: String -> Maybe Int
validate s = isValid (reads s)
   where isValid []            = Nothing
         isValid ((n, _):_) 
               | outOfBounds n = Nothing
               | otherwise     = Just n
         outOfBounds n = (n < 1) || (n > length choices)

choices :: [(Int, (String, IO ()))]
choices = zip [1.. ] [
   ("DoSomething", foo)
 , ("Quit", bar)
 ]

execute :: Int -> IO ()
execute n = doExec $ filter (\(i, _) -> i == n) choices
   where doExec ((_, (_,f)):_) = f

foo = undefined
bar = undefined

You could probably split the enumerating in "choices" so you only have the descriptions and functions inside it, a little bit of separation, but this works. Evaluating the "menu" function will let you choose what to do!

LukeN
so easy :) one more question : could You explain first line : (putSrtln .....) Im newbie in haskell and dont know that operators like . $ and so on. If it isnt a problem for You it would help me.
gruber
I just edited my answer and made it more flexible! Not tested in any ways, I just wrote it down, but you should see what I did there :). About that putStrLn line: I basically wrote "putStrLn (unlines (map concatNums choices))" without the parantheses :)
LukeN
Okay, tested now, works like a charm!
LukeN
Whew, I just can't post incomplete pieces of code - added checking for bad, non numbered input and out of bounds input :)
LukeN
+6  A: 

There's a few cool packages for high level ways to construct command line systems in general:

  1. ui-command: A framework for friendly commandline programs
  2. haskeline: A command-line interface for user input, written in Haskell.
  3. HCL: High-level library for building command line interfaces.

I particularly like ui-command, as it is an entire framework for your command line tools: It will dispatch to handler functions that you provide for each command, and also provide command-specific help to the user.

The goal is a polished feeling, rather than a hackish feeling.

Don Stewart
hi, thanks for help, after downloading that package ui-command and alldependencies when Im trying to compile example I have error:Could not find module `Control.Monad.Trans': it was found in multiple packages: monads-fd-0.1.0.1 mtl-1.1.0.2How can I solve this ?
gruber
You'll need to unregister monads-fd I think. ghc-pkg unregister monads-fd.
Don Stewart
A: 

Here's another example that is a little more menu-like, in that it reads single characters in reacts directly, without requiring the user to press enter.

import System.IO
import System.Exit

import Control.Monad


main = forever (printMenu >> readChoice >>= menuAction)

printMenu = putStr "\np)rint 'Hello, world!'\ne)xit\nyour choice: " >> hFlush stdout

readChoice = hSetBuffering stdin NoBuffering >> hSetEcho stdin False >> getChar

menuAction 'p' = putStrLn "\nHello, world!"
menuAction 'e' = exitSuccess
menuAction _ = hPutStrLn stderr "\nInvalid choice."
jkramer