Reputation: 3701
I have the following code:
import System.Environment
import System.Directory
import System.IO
import Data.List
dispatch :: [(String, [String] -> IO ())]
dispatch = [ ("add", add)
, ("view", view)
, ("remove", remove)
, ("bump", bump)
]
main = do
(command:args) <- getArgs
let result = lookup command dispatch
if result == Nothing then
errorExit
else do
let (Just action) = result
action args
errorExit :: IO ()
errorExit = do
putStrLn "Incorrect command"
add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n")
view :: [String] -> IO ()
view [fileName] = do
contents <- readFile fileName
let todoTasks = lines contents
numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks
putStr $ unlines numberedTasks
remove :: [String] -> IO ()
remove [fileName, numberString] = do
handle <- openFile fileName ReadMode
(tempName, tempHandle) <- openTempFile "." "temp"
contents <- hGetContents handle
let number = read numberString
todoTasks = lines contents
newTodoItems = delete (todoTasks !! number) todoTasks
hPutStr tempHandle $ unlines newTodoItems
hClose handle
hClose tempHandle
removeFile fileName
renameFile tempName fileName
bump :: [String] -> IO ()
bump [fileName, numberString] = do
handle <- openFile fileName ReadMode
(tempName, tempHandle) <- openTempFile "." "temp"
contents <- hGetContents handle
let number = read numberString
todoTasks = lines contents
bumpedItem = todoTasks !! number
newTodoItems = [bumpedItem] ++ delete bumpedItem todoTasks
hPutStr tempHandle $ unlines newTodoItems
hClose handle
hClose tempHandle
removeFile fileName
renameFile tempName fileName
Trying to compile it gives me the following error:
$ ghc --make todo
[1 of 1] Compiling Main ( todo.hs, todo.o )
todo.hs:16:15:
No instance for (Eq ([[Char]] -> IO ()))
arising from a use of `=='
Possible fix:
add an instance declaration for (Eq ([[Char]] -> IO ()))
In the expression: result == Nothing
In a stmt of a 'do' block:
if result == Nothing then
errorExit
else
do { let (Just action) = ...;
action args }
In the expression:
do { (command : args) <- getArgs;
let result = lookup command dispatch;
if result == Nothing then
errorExit
else
do { let ...;
.... } }
I don't get why is that since lookup
returns Maybe a
, which I'm surely can compare to Nothing
.
Upvotes: 2
Views: 3190
Reputation: 36622
The type of the (==)
operator is Eq a => a -> a -> Bool
. What this means is that you can only compare objects for equality if they're of a type which is an instance of Eq
. And functions aren't comparable for equality: how would you write (==) :: (a -> b) -> (a -> b) -> Bool
? There's no way to do it.1 And while clearly Nothing == Nothing
and Just x /= Nothing
, it's the case that Just x == Just y
if and only if x == y
; thus, there's no way to write (==)
for Maybe a
unless you can write (==)
for a
.
There best solution here is to use pattern matching. In general, I don't find myself using that many if
statements in my Haskell code. You can instead write:
main = do (command:args) <- getArgs
case lookup command dispatch of
Just action -> action args
Nothing -> errorExit
This is better code for a couple of reasons. First, it's shorter, which is always nice. Second, while you simply can't use (==)
here, suppose that dispatch
instead held lists. The case
statement remains just as efficient (constant time), but comparing Just x
and Just y
becomes very expensive. Second, you don't have to rebind result
with let (Just action) = result
; this makes the code shorter and doesn't introduce a potential pattern-match failure (which is bad, although you do know it can't fail here).
1:: In fact, it's impossible to write (==)
while preserving referential transparency. In Haskell, f = (\x -> x + x) :: Integer -> Integer
and g = (* 2) :: Integer -> Integer
ought to be considered equal because f x = g x
for all x :: Integer
; however, proving that two functions are equal in this way is in general undecidable (since it requires enumerating an infinite number of inputs). And you can't just say that \x -> x + x
only equals syntactically identical functions, because then you could distinguish f
and g
even though they do the same thing.
Upvotes: 10
Reputation: 41618
The Maybe a
type has an Eq
instance only if a
has one - that's why you get No instance for (Eq ([[Char]] -> IO ()))
(a function can't be compared to another function).
Maybe the maybe function is what you're looking for. I can't test this at the moment, but it should be something like this:
maybe errorExit (\action -> action args) result
That is, if result
is Nothing
, return errorExit
, but if result
is Just action
, apply the lambda function on action
.
Upvotes: 8