I'm approaching Haskell with a view of converting runtime errors to compile-time errors. I expect the compiler to figure out that all the code paths in the following code are not error free. If authorizeUser returns UnauthorizedCommand then the call to (command $ authorizeUser cmd userId) will fail during runtime.
data Command = UnauthorizedCommand | Command {command :: String, userId :: String}
authorizedUsers = ["1", "2", "3"]
authorizeUser :: String -> String -> Command
authorizeUser cmd userId = if (userId `elem` authorizedUsers) then Command {command=cmd, userId=userId} else UnauthorizedCommand
main :: IO ()
main = do
cmd <- getLine
userId <- getLine
case (command $ authorizeUser cmd userId) of
"ls" -> putStrLn "works"
_ -> putStrLn "not authorized"
Is there a compiler switch to enable such checking?
The main problem in this example is that using the record syntax in a data type with multiple constructors can end up with record selector functions that are partial. It's therefore not recommended to ever mix records and multiple constructors unless all constructors have identical record fields.
The simplest solution is to not use record syntax here and to define your own command accessor which makes the error case explicit with a Maybe return type:
data Command = UnauthorizedCommand | Command String String
command :: Command -> Maybe String
command UnauthorizedCommand = Nothing
command (Command cmd _) = Just cmd
A slightly more complex way to check this during compile time is to use GADTs to encode the permission level in the types.
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
data Permission
= Authorized | Unauthorized
data Command p where
UnauthorizedCommand :: Command Unauthorized
Command :: {command :: String, userId :: String} -> Command Authorized
Now the type of the command selector is Command Authorized -> String so you get a compile time error if you try to use it with an unauthorized command.
You can use the "case" only on authorizeUser and then filter the DATA Command
data Command = UnauthorizedCommand | Command {command :: String, userId :: String}
authorizedUsers = ["1", "2", "3"]
authorizeUser :: String -> String -> Command
authorizeUser cmd userId = if (userId `elem` authorizedUsers) then Command {command=cmd, userId=userId} else UnauthorizedCommand
main :: IO ()
main = do
cmd <- getLine
userId <- getLine
case authorizeUser cmd userId of
Command "ls" _ -> putStrLn "works"
_ -> putStrLn "not authorized" -- either the user id is not good or the command is not good.
If you have several command, you can also do:
main = do
cmd <- getLine
userId <- getLine
case authorizeUser cmd userId of
Command _ _ -> case cmd of
"ls" -> putStrLn "works"
"cd" -> putStrLn "moved!"
_ -> putStrLn "Bad command!"
_ -> putStrLn "not authorized" -- or: Unauthorized -> putStrLn "Not authorized"
PS : Note you can factor the putStrLns
I think I've finally understand your question: you want Haskell generate error/warning when calling command on an arbitrary Command. It is related to this answer : https://stackoverflow.com/a/3804900/3421913 . In short:
It is not possible to check (or very very hard) at compile time if a command is Command Str Str or Unauthorized: in your case it is quite simple, but it might be more complex.
You sometimes know that your value has the good property but it's difficult to express that in haskell (see the linked answer above with the duplicates/remove_duplicates functions). In your case, authorizeUser cmd userId is unsafe (since cmd and userId are user input), so we won't use the command function which will fail if the user is a monkey. Instead, we'll use the pattern matching I've proposed in my other answer.
If you have some invariant ensuring that c::Command is not Unauthorized, you might use directly command.
Related
I am working on a little project in Haskell and currently I am trying to make it more extensible by adding the possibility of using optional cli flags.
The desired interface would look as this:
program: [--command1 {n} ] [--help]
with command1 and help being optional arguments, and furthermore, command1 can also take an integer argument n.
Using patterm matching, I came up with the following result
main :: IO ()
main = do
args <- getArgs
case args of
[] -> command1'
["--command1", a] -> if isJust (readMaybe n::Maybe Int)
then command1 a-- handle proper integer
else -- call command1' but also show a warning message
["--command1"] -> command1'
["--help"] -> putStrLn showWarningMessage
_ -> putStrLn (showErrorMessage args) -- show an error message
As the program does not rely on a certain parameter to be present, it should be able to run even if the cli arguments aren't valid.
I am unsure how to handle the following situations:
How to handle the case when a is not an integer and thus, we want to warn the user but also call command1'?
What to do in the situation when wrong arguments are added. The pattern match is exhaustive so arguments as --command2 would be captured but what to do if args is of the form args=[--command2, --command1, n] or args=[--command1, --command2]. In both of this cases ideally an warning would be printed regarding command2 being unknown but the cli should accept command1.
Note: I saw that there are libraries specifically designed to handle these issues but I am interested in solving these simple cases in order to learn Haskell better. Of course, there could be more edge cases but for my usage, the two mentioned are my only concern.
Not an expert, but here are my answers:
1. What would be working for you is a simple do notation in your else, and provide a default value for the call to command1, since I assume your function needs an Integer to do something. Which would end up something like that :
main :: IO ()
main = do
args <- getArgs
case args of
[] -> command1'
["--command1", a] -> if isJust (readMaybe n::Maybe Int)
then command1 a
-- here what I think you want to do
else do
-- here your warning message
putStrLn (showWarningMessage args)
-- here let's say 10 is the default value
command1 10
["--command1"] -> command1'
["--help"] -> putStrLn showWarningMessage
_ -> putStrLn (showErrorMessage args) -- show an error message
I found that in http://www.happylearnhaskelltutorial.com/1/times_table_train_of_terror.html#s18.4 which you can begin from start, it explains well the concepts of Haskell.
2. I understand what your are trying to do, but isn't the purpose of a commandline prompt to fail fast ? Keep in mind that you must focus on what are the default values, and doing so, what should be the default behaviour of your tool. And maybe you can think of a different implementation of your params. This is what I learned from few months of Haskell, coming myself from imperative languages.
Anyway I have some hints about how to do this. And what if you add a command3 ? Your approach with describing all the possible use cases and manage everything by hand can lead easily to oversights and bugs. Without using an external library, this management can be avoided by using System.Console.GetOpt, the built-in parser of Haskell. Even if the documentation is a bit old they explain some concepts about flags and parsing.
I also found this post on Stack Overflow which shows a working example https://stackoverflow.com/a/10816382/6270743 with GetOpt. Based on that, I was not able to show an error message AND execute the command which is what you try to do.
But the code, even not perfect, handles perfectly every combination of flags/params and catches all the errors with 4 different params. Here it is with some comments :
import Control.Monad (foldM)
import System.Console.GetOpt
import System.Environment ( getArgs, getProgName )
data Options = Options {
optVerbose :: Bool
, optShowVersion :: Bool
, optNbOfIterations :: Int
, optSomeString :: String
} deriving Show
defaultOptions = Options {
optVerbose = False
, optShowVersion = False
, optNbOfIterations = 1
, optSomeString = "a random string"
}
options :: [OptDescr (Options -> Either String Options)]
options =
[ Option ['v'] ["verbose"]
(NoArg (\ opts -> Right opts { optVerbose = True }))
"chatty output on stderr"
, Option ['V','?'] ["version"]
(NoArg (\ opts -> Right opts { optShowVersion = True })) -- here no need for a value for this option because of `NoArg`
"show version number"
, Option ['i'] ["iterations"]
(ReqArg (\ i opts -> -- here the value of the param is required `ReqArg`, but you can also have `OptArg` which makes the value optional
case reads i of
[(iterations, "")] | iterations >= 1 && iterations <= 20 -> Right opts { optNbOfIterations = iterations } -- here your conditions
_ -> Left "--iterations must be a number between 1 and 20" -- error message in case previous conditions are not met
) "ITERATIONS") -- here the name of the param in the cli help
"Number of times you want to repeat the reversed string" -- here the description of param's usage in the cli help
, Option ['s'] ["string"]
(ReqArg (\arg opts -> Right opts { optSomeString = return arg !! 0 } -- really dirty, sorry but could not make it work another way
"Outputs the string reversed"
]
parseArgs :: IO Options
parseArgs = do
argv <- getArgs
progName <- getProgName
let header = "Usage: " ++ progName ++ " [OPTION...]"
let helpMessage = usageInfo header options
case getOpt RequireOrder options argv of
(opts, [], []) ->
case foldM (flip id) defaultOptions opts of
Right opts -> return opts
Left errorMessage -> ioError (userError (errorMessage ++ "\n" ++ helpMessage))
(_, _, errs) -> ioError (userError (concat errs ++ helpMessage))
main :: IO ()
main = do
options <- parseArgs
if isVerbose options
then mapM_ putStrLn ["verbose mode activated !","You will have a lot of logs","With a lot of messages","And so on ..."]
else return () -- do nothing
mapM_ print (repeatReversedString options)
if getVersion options
then print "version : 0.1.0-prealpha"
else return ()
return ()
-- With those functions we extract the option setting from the data type Options (and maybe we play with it)
isVerbose :: Options -> Bool
isVerbose (Options optVerbose _ _ _) = optVerbose
getVersion :: Options -> Bool
getVersion (Options _ optShowVersion _ _) = optShowVersion
repeatReversedString :: Options -> [String]
repeatReversedString (Options _ _ optNbOfIterations optSomeString) =
replicate optNbOfIterations (reverse optSomeString)
getString :: Options -> String
getString (Options _ _ _ optSomeString) = optSomeString
I think you can achieve your goal by using OptArg (instead ofthe NoArg and ReqArg I used), which has this definition :
OptArg (Maybe String -> a) String
Here is the link on hackage :
https://hackage.haskell.org/package/base-4.14.1.0/docs/System-Console-GetOpt.html
Remember that Haskell is very different from other common languages as for example you have no for loops !
I'm doing my first steps using Haskell. I created a project using stack and changed the Main.hs into
module Main where
my_fkt :: String -> String
my_fkt input = show (length input)
main :: IO ()
main = interact my_fkt
I build the project via stack build, run it via stack exec firststeps-exe, enter "abcd" and finish input via <CTRL>-D. In the console I now see
abcd4%
The %is inverted. If I use a text file containing the "abcd" (without line break) and execute more sample.txt | stack exec firststeps-exe I see
abcd5%
Why do I get one additional character in the second case and what is the inverted percentage sign?
That is because the definition of interact uses putStr instead of putStrLn.
You can take a look at the source code here.
interact :: (String -> String) -> IO ()
interact f = do s <- getContents
putStr (f s)
To remedy your issue I would go on and create a similar function
interact' :: (String -> String) -> IO ()
interact' f = do s <- getContents
putStrLn (f s)
or if you like to mix it up and write a bit terser code
interact' f = putStrLn =<< (f <$> getContents)
I don't know what the % is or why it is showing up, my guess would be that it is the escaped CTRL-D.
With regards to your second question about the additional "non-existing" character, I am also not sure, but here my guess would be that this is the \EOF.
Btw. you can always check using more testinput | wc -c it should yield the same result as your haskell program.
///Edit
I have a problem with haskell. If someone can help me, that would be great.
I'm inserting records into a file using the following code:
check :: Int -> Int
check a
|a > 0 && a<=10 = a
|otherwise = error "oh. hmm.. enter a number from the given interval"
ame :: IO ()
ame = do
putStr "Enter the file name: "
name <- getLine
putStrLn "Do you want to add new records? "
question <- getLine
if question == "yes" then do
putStrLn "Enter your records:"
newRec <- getLine
appendFile name ('\n':newRec)
--new lines--
putStrLn "enter a number between 0 and 10: "
something <- getLine
return (read something:: Int)
let response = check something
putStrLn response
appendFile name ('\n':something)
putStrLn "enter something new again: "
something2 <- getLine
appendFile name ('\n':something2)
putStrLn "a"
else
putStr "b"
Now I want to extract some records from this file, using a specific criteria. For example, i want to extract and display records from even(or odd or any other criteria) rows. can I do that? if yes, how?
Also...
I want to check the user input. Let's say that I don't want him/her to enter a string instead of an integer. can I also check his/her input? do I need to create another function and call that function inside the code from above?
///Edit
thank you for answering. but how can i embed it into my previous code?
I've tried now to create a function(you can see the above code) and then call that function in my IO. but it doesn't work..
Yes, it is certainly possible to display only certain rows. If you want to base it off of the row number, the easiest way is to use zip and filter
type Record = String
onlyEven :: [Record] -> [Record]
onlyEven records =
map snd $ -- Drop the numbers and return the remaining records
filter (even . fst) $ -- Filter by where the number is even
zip [1..] -- All numbers
records -- Your records
This technique can be used in a lot of circumstances, you could even abstract it a bit to
filterByIdx :: Integral i => (i -> Bool) -> [a] -> [a]
filterByIdx condition xs = map snd $ filter (condition . fst) $ zip [1..] xs
-- Could also use 0-based of `zip [0..] xs`, up to you
onlyEven :: [a] -> [a]
onlyEven = filterByIdx even
If you want to check if an input is an Int, the easiest way is to use the Text.Read.readMaybe function:
import Text.Read (readMaybe)
promptUntilInt :: IO Int
promptUntilInt = do
putStr "Enter an integer: "
response <- getLine
case readMaybe response of
Just x -> return x
Nothing -> do
putStrLn "That wasn't an integer!"
promptUntilInt
This should give you an idea of how to use the function. Note that in some cases you'll have to specify the type signature manually as case (readMaybe response :: Maybe Int) of ..., but here it'll work fine because it can deduce the Int from promptUntilInt's type signature. If you get an error about how it couldn't figure out which instance for Read a to use, you need to manually specify the type.
You have
something <- getLine
return (read something:: Int)
let response = check something
putStrLn response
To step through what you're trying to do with these lines:
something <- getLine
getLine has the type IO String, meaning it performs an IO action and returns a String. You can extract that value in do notation as
something <- getLine
Just as you have above. Now something is a String that has whatever value was entered on that line. Next,
return (read something :: Int)
converts something to an Int, and then passes it to the function return. Remember, return is not special in Haskell, it's just a function that wraps a pure value in a monad. return 1 :: Maybe Int === Just 1, for example, or return 1 :: [Int] === [1]. It has contextual meaning, but it is no different from the function putStrLn. So that line just converts something to an Int, wraps it in the IO monad, then continues on to the next line without doing anything else:
let response = check something
This won't compile because check has the type Int -> Int, not String -> String. It doesn't make any sense to say "hello, world" > 0 && "hello, world" <= 10, how do you compare a String and an Int? Instead, you want to do
let response = check (read something)
But again, this is unsafe. Throwing an error on an invalid read or when read something is greater than 10 will crash your program completely, Haskell does errors differently than most languages. It's better to do something like
check :: Int -> Bool
check a = a > 0 && a <= 10
...
something <- getLine
case readMaybe something of
Nothing -> putStrLn "You didn't enter a number!"
Just a -> do
if check a
then putStrLn "You entered a valid number!"
else putStrLn "You didn't enter a valid number!"
putStrLn "This line executes next"
While this code is a bit more complex, it's also safe, it won't ever crash and it handles each case explicitly and appropriately. By the way, the use of error is usually considered bad, there are limited capabilities for Haskell to catch errors thrown by this function, but errors can be represented by data structures like Maybe and Either, which give us pure alternatives to unsafe and unpredictable exceptions.
Finally,
putStrLn response
If it was able to compile, then response would have the type Int, since that's what check returns. Then this line would have a type error because putStrLn, as the name might suggest, puts a string with a new line, it does not print Int values. For that, you can use print, which is defined as print x = putStrLn $ show x
Since this is somewhat more complex, I would make a smaller function to handle it and looping until a valid value is given, something like
prompt :: Read a => String -> String -> IO a
prompt msg failMsg = do
putStr msg
input <- getLine
case readMaybe input of
Nothing -> do
putStrLn failMsg
prompt
Just val -> return val
Then you can use it as
main = do
-- other stuff here
-- ...
-- ...
(anInt :: Int) <- prompt "Enter an integer: " "That wasn't an integer!"
-- use `anInt` now
if check anInt
then putStrLn $ "Your number multiplied by 10 is " ++ show (anInt * 10)
else putStrLn "The number must be between 1 and 10 inclusive"
You don't have to make it so generic, though. You could easily just hard code the messages and the return type like I did before with promptUntilInt.
I'm trying to use the interact function, but I'm having an issue with the following code:
main::IO()
main = interact test
test :: String -> String
test [] = show 0
test a = show 3
I'm using EclipseFP and taking one input it seems like there is an error. Trying to run main again leads to a:
*** Exception: <stdin>: hGetContents: illegal operation (handle is closed)
I'm not sure why this is not working, the type of test is String -> String and show is Show a => a -> String, so it seems like it should be a valid input for interact.
EDIT/UPDATE
I've tried the following and it works fine. How does the use of unlines and lines cause interact to work as expected?
main::IO()
main = interact respondPalindromes
respondPalindromes :: String -> String
respondPalindromes =
unlines .
map (\xs -> if isPal xs then "palindrome" else "not a palindrome") .
lines
isPal :: String -> Bool
isPal xs = xs == reverse xs
GHCi and Unsafe I/O
You can reduce this problem (the exception) to:
main = getContents >> return ()
(interact calls getContents)
The problem is that stdin (getContents is really hGetContents stdin) remains evaluated in GHCi in-between calls to main. If you look up stdin, it's implemented as:
stdin :: Handle
stdin = unsafePerformIO $ ...
To see why this is a problem, you could load this into GHCi:
import System.IO.Unsafe
f :: ()
f = unsafePerformIO $ putStrLn "Hi!"
Then, in GHCi:
*Main> f
Hi!
()
*Main> f
()
Since we've used unsafePerformIO and told the compiler that f is a pure function, it thinks it doesn't need to evaluate it a second time. In the case of stdin, all of the initialization on the handle isn't run a second time and it's still in a semi-closed state (which hGetContents puts it in), which causes the exception. So I think that GHCi is "correct" in this case and the problem lies in the definition of stdin which is a practical convenience for compiled programs that will just evaluate stdin once.
Interact and Lazy I/O
As for why interact quits after a single line of input while the unlines . lines version continues, let's try reducing that as well:
main :: IO ()
main = interact (const "response\n")
If you test the above version, interact won't even wait for input before printing response. Why? Here's the source for interact (in GHC):
interact f = do s <- getContents
putStr (f s)
getContents is lazy I/O, and since f in this case doesn't need s, nothing is read from stdin.
If you change your test program to:
main :: IO ()
main = interact test
test :: String -> String
test [] = show 0
test a = show a
you should notice different behavior. And that suggests that in your original version (test a = show 3), the compiler is smart enough to realize that it only needs enough input to determine if the string read is empty or not (because if it's not empty, it doesn't need to know what a is, it just needs to print "3"). Since the input is presumably line-buffered on a terminal, it reads up until you press the return key.
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).