Haskeline word completion with list - haskell

Haskell newbie here and also first time asking a question here, apologies in advance for anything I may have missed anything.
Im writing a repl function that takes user input, adds the input to a list and uses haskeline to tab complete user input with previously typed words.
So far I can only complete words with a static list defined before run time.
1- How would I add the input word to the list in repl (SOLVED...?)
2- How would I enable the searchFunc to query the list in the repl loop?
My current code:
import Data.List
import System.Console.Haskeline
-- main method
main :: IO ()
main = runInputT mySettings (repl [])
repl :: [String] -> InputT IO ()
repl list =
do
minput <- getInputLine "> "
case minput of
Nothing -> do
outputStrLn "Error"
repl list
Just input ->
do
outputStrLn "Successfully added input to list"
-- < 1- add input word to list ... is this correct?>
repl (input:list)
mySettings :: Settings IO
mySettings = Settings { historyFile = Just "myhist",
complete = completeWord Nothing " \t" $ return . searchFunc,
autoAddHistory = True
}
searchFunc :: String -> [Completion]
-- < 2- complete word using list of words from repl>
searchFunc str = map simpleCompletion $ filter (str `isPrefixOf`) listFromRepl
Any help is appreciated, thanks in advance.

Related

How to correctly parse arguments with Haskell?

I'm trying to learn how to work with IO in Haskell by writing a function that, if there is a flag, will take a list of points from a file, and if there is no flag, it asks the user to enter them.
dispatch :: [String] -> IO ()
dispatch argList = do
if "file" `elem` argList
then do
let (path : otherArgs) = argList
points <- getPointsFile path
else
print "Enter a point in the format: x;y"
input <- getLine
if (input == "exit")
then do
print "The user inputted list:"
print $ reverse xs
else (inputStrings (input:xs))
if "help" `elem` argList
then help
else return ()
dispatch [] = return ()
dispatch _ = error "Error: invalid args"
getPointsFile :: String -> IO ([(Double, Double)])
getPointsFile path = do
handle <- openFile path ReadMode
contents <- hGetContents handle
let points_str = lines contents
let points = foldl (\l d -> l ++ [tuplify2 $ splitOn ";" d]) [] points_str
hClose handle
return points
I get this: do-notation in pattern Possibly caused by a missing 'do'?` after `if "file" `elem` argList.
I'm also worried about the binding issue, assuming that I have another flag that says which method will be used to process the points. Obviously it waits for points, but I don't know how to make points visible not only in if then else, constructs. In imperative languages I would write something like:
init points
if ... { points = a}
else points = b
some actions with points
How I can do something similar in Haskell?
Here's a fairly minimal example that I've done half a dozen times when I'm writing something quick and dirty, don't have a complicated argument structure, and so can't be bothered to do a proper job of setting up one of the usual command-line parsing libraries. It doesn't explain what went wrong with your approach -- there's an existing good answer there -- it's just an attempt to show what this kind of thing looks like when done idiomatically.
import System.Environment
import System.Exit
import System.IO
main :: IO ()
main = do
args <- getArgs
pts <- case args of
["--help"] -> usage stdout ExitSuccess
["--file", f] -> getPointsFile f
[] -> getPointsNoFile
_ -> usage stderr (ExitFailure 1)
print (frobnicate pts)
usage :: Handle -> ExitCode -> IO a
usage h c = do
nm <- getProgName
hPutStrLn h $ "Usage: " ++ nm ++ " [--file FILE]"
hPutStrLn h $ "Frobnicate the points in FILE, or from stdin if no file is supplied."
exitWith c
getPointsFile :: FilePath -> IO [(Double, Double)]
getPointsFile = {- ... -}
getPointsNoFile :: IO [(Double, Double)]
getPointsNoFile = {- ... -}
frobnicate :: [(Double, Double)] -> Double
frobnicate = {- ... -}
if in Haskell doesn't inherently have anything to do with control flow, it just switches between expressions. Which, in Haskell, happen to include do blocks of statements (if we want to call them that), but you still always need to make that explicit, i.e. you need to say both then do and else do if there are multiple statements in each branch.
Also, all the statements in a do block need to be indented to the same level. So in your case
if "file" `elem` argList
...
if "help" `elem` argList
Or alternatively, if the help check should only happen in the else branch, it needs to be indented to the statements in that do block.
Independent of all that, I would recommend to avoid parsing anything in an IO context. It is usually much less hassle and easier testable to first parse the strings into a pure data structure, which can then easily be processed by the part of the code that does IO. There are libraries like cmdargs and optparse-applicative that help with the parsing part.

Haskell: Handling resulting Either from computations

I have revisited Haskell lateley and constructed a toy programming language parser/interpreter. Using Parsec for lexing and parsing and a separate interpreter. I'm running in to some issues with feeding the result from the parser to my interpreter and handle the potential error from both the interpreter and parser. I end up with something like this:
main = do
fname <- getArgs
input <- readFile (head fname)
case lparse (head fname) input of
Left msg -> putStrLn $ show msg
Right p -> case intrp p of
Left msg -> putStrLn $ show msg
Right r -> putStrLn $ show r
This dosn't look pretty at all. My problem is that lparse returns Either ParseError [(String, Stmt)] and itrp returns the type Either ItrpError Stmt so I'm having a real hard time feeding the Right result from the parser to the interpreter and at the same time bail and print the possible ParseError or IntrpError.
The closest to what i want is something like this
main = do
fname <- getArgs
input <- readFile (head fname)
let prog = lparse (head fname) input
(putStrLn . show) (intrp <$> prog)
But this will not surprisingly yield a nested Either and not print pretty either.
So are there any nice Haskell ideomatic way of doing this threading results from one computation to another and handling errors (Lefts) in a nice way without nesting cases?
Edit
adding types of lparse and itrp
lparse :: Text.Parsec.Pos.SourceName -> String -> Either Text.Parsec.Error.ParseError [([Char], Stmt)]
intrp :: [([Char], Stmt)] -> Either IntrpError Stmt
While not perfect, I'd create a helper function for embedding any Showable error from Either into MonadError:
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad.Except
strErr :: (MonadError String m, Show e) => Either e a -> m a
strErr = either (throwError . show) return
Then if you have a computation that can fail with errors, like
someFn :: ExceptT String IO ()
someFn = strErr (Left 42)
you can run it (printing errors to stdout) as
main :: IO ()
main = runExceptT someFn >>= either putStrLn return
In your case it'd be something like
main = either putStrLn return <=< runExceptT $ do
fname <- liftIO getArgs
input <- liftIO $ readFile (head fname)
prog <- strErr $ lparse (head fname) input
r <- strErr $ interp prog
print r
Well, if you want to chain successful computations, you can always use >>= to do that. For instance in your case:
lparse (head fname) input >>= intrp
And if you want to print out either your error message you can use the either class that takes two handler functions, one for the case when you have Left a (error in your case) and another for Right b (in your case a successful thing). An example:
either (putStrLn . show) (putStrLn . show) (lparse (head fname) input >>= intrp)
And if anything fails in your chain (any step of your monadic chain becomes Left a) it stops and can for instance print out the error message in the above case.

Strange error using Data.Map in haskell

I'm trying to write a really simple editor like "ed".
In this program I'm trying to use a mapping for building the control which translate string-command in actions to perform.
Here's a piece of code:
commands :: Map String ([Handle] -> IO ())
commands = fromAscList [
("o",\list -> print "Insert name of the file to be opened" >> getLine >>= \nomefile ->
openFile nomefile ReadWriteMode >>= \handle -> editor (handle:list)),
("i",\list -> case list of { [] -> print "No buffer open" ; handle:res -> write handle } >> editor list),
("q",\list -> if list == [] then return () else mapM_ hClose list >> return ())
]
editor :: [Handle] -> IO()
editor list = do
command <- getLine
let action = lookup command commands
case action of
Nothing -> print "Unknown command" >> editor list
Just act -> act list
The problem is that when I execute the editor function, either in ghci or in an executable file, when I type "o" I get the message "Unknown command" instead of the call to the function to open the file. I've tryed the same code using associative list instead of Map and in this case it works. So what could be the problem here?
What's more odd is that if I call keys on the mapping commands in ghci it return a list containing the string "o" too.
I thank in advance for any help.
commands :: Map String ([Handle] -> IO ())
commands = fromAscList [
("o",_),
("i",_),
("q",_)
]
But
ghci> Data.List.sort ["o","i","q"]
["i","o","q"]
You were lying to Data.Map, so it constructed a Map that didn't satisfy the required invariants. Thus looking up things in the Map didn't work, since the request was sent down the wrong branch (sometimes).

Haskell case statement

I have code something like this
main :: [[String]] -> IO ()
main st = do
answer <- getLine
case answer of
"q" -> return ()
"load" x -> main $ parseCSV $ readFile x
This doesn't work, so my question is how can I use case switch statement for something of changing input
For example in my code I want the input from a user to be either q or a load, but the load will constant change:
load "sample.csv"
load "test.csv"
load "helloworld.csv"
In my code I indicated the constantly changing input as X, but this doesn't work as I expected it.
Help would be appreciated, thank you.
As others have mentioned, the problem is with your pattern matching.
Here's a simple way to get around this (and still have something readable).
Split answer into words for matching (with the words function).
Use the first word in the pattern match.
If you want to use the remaining "words", simply unwords the remaining elems in the list to get a string.
Example:
main :: IO ()
main = do
answer <- getLine
case words answer of
("q":_) -> putStrLn "I'm quitting!"
("load":x) -> putStrLn ("Now I will load " ++ unwords x)
otherwise -> putStrLn "Not sure what you want me to do!"
Note - the x you had above is actually unwords x here.

Idiomatic way to conditionally process IO in Haskell

I'm writing a little shell script in Haskell which can take an optional argument. However, if the argument is not present, I'd like to get a line from stdin in which to ask for a value.
What would be the idiomatic way to do this in Haskell?
#!/usr/bin/env runhaskell
import Control.Applicative ((<$>))
import Data.Char (toLower)
import IO (hFlush, stdout)
import System.Environment (getArgs)
main :: IO ()
main = do args <- getArgs
-- here should be some sort of branching logic that reads
-- the prompt unless `length args == 1`
name <- lowerCase <$> readPrompt "Gimme arg: "
putStrLn name
lowerCase = map toLower
flushString :: String -> IO ()
flushString s = putStr s >> hFlush stdout
readPrompt :: String -> IO String
readPrompt prompt = flushString prompt >> getLine
Oh, and if there's a way to do it with something from Control.Applicative or Control.Arrow I'd like to know. I've become quite keen on these two modules.
Thanks!
main :: IO ()
main = do args <- getArgs
name <- lowerCase <$> case args of
[arg] -> return arg
_ -> readPrompt "Gimme arg: "
putStrLn name
This doesn't fit your specific use case, but the question title made me think immediately of when from Control.Monad. Straight from the docs:
when :: Monad m => Bool -> m () -> m ()
Conditional execution of monadic expressions.
Example:
main = do args <- getArgs
-- arg <- something like what FUZxxl did..
when (length args == 1) (putStrLn $ "Using command line arg: " ++ arg)
-- continue using arg...
You can also use when's cousin unless in similar fashion.

Resources