2

我有以下代码:

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

尝试编译它会给我以下错误:

$ 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 ...;
                    .... } }

我不明白为什么会这样,因为lookup返回Maybe a,我肯定可以与之比较Nothing

4

2 回答 2

9

运算符的类型(==)Eq a => a -> a -> Bool。这意味着您只能比较对象的相等性,如果它们的类型是Eq. 并且函数不能用于相等性:你会怎么写(==) :: (a -> b) -> (a -> b) -> Bool?没有办法做到这一点。1 虽然很清楚Nothing == Nothingand Just x /= Nothing,但情况是Just x == Just y当且仅当x == y; 因此,除非您可以(==)为.Maybe a(==)a

这里最好的解决方案是使用模式匹配。一般来说,我发现自己if在 Haskell 代码中没有使用那么多语句。你可以改为写:

main = do (command:args) <- getArgs
          case lookup command dispatch of
            Just action -> action args
            Nothing     -> errorExit

由于几个原因,这是更好的代码。首先,它更短,这总是很好。其次,虽然您根本不能在这里使用(==),但假设它dispatch改为持有列表。该case语句仍然有效(恒定时间),但比较Just x变得Just y非常昂贵。其次,您不必重新绑定result; let (Just action) = result这使代码更短,并且不会引入潜在的模式匹配失败(这很糟糕,尽管您知道它不会在这里失败)。


1: : 事实上,(==)在保持引用透明性的同时写是不可能的。在 Haskell 中,f = (\x -> x + x) :: Integer -> Integer应该g = (* 2) :: Integer -> Integer被认为是平等的,因为f x = g x对于所有人x :: Integer;但是,以这种方式证明两个函数相等通常是不可判定的(因为它需要枚举无限数量的输入)。而且你不能只说它\x -> x + x等于语法上相同的函数,因为这样你就可以区分它们fg即使它们做同样的事情。

于 2012-07-31T17:48:45.830 回答
8

只有当有一个实例时,该Maybe a类型才有一个实例——这就是你得到的原因(一个函数不能与另一个函数进行比较)。EqaNo instance for (Eq ([[Char]] -> IO ()))

也许也许功能是您正在寻找的。我目前无法对此进行测试,但应该是这样的:

maybe errorExit (\action -> action args) result

也就是说,如果resultNothing,则返回errorExit,但如果resultJust action,则将 lambda 函数应用于action

于 2012-07-31T17:38:25.777 回答