Haskell - 模糊的类功能

use*_*183 2 haskell

我正在制作控制台程序,我将大量使用文本菜单.我写了一个Menu带有函数的类,choices它返回可能的菜单选项parseChoice字符串和将用户输入的字符串转换为菜单项的函数.

data MainMenu = FirstItem | SecondItem

class Menu a where
  choices :: String -- ERROR HERE
  parseChoice :: String -> Maybe a

instance Menu MainMenu where
  choices = "1) first choice\n2) second choice"
  parseChoice "1" = Just FirstItem
  parseChoice "2" = Just SecondItem
  parseChoice _ = Nothing

getMenuItem :: Menu a => IO a
getMenuItem = do
  putStrLn choices -- ERROR HERE
  choice <- getLine
  case parseChoice choice of
    Just item -> return item
    Nothing -> getMenuItem

main :: IO ()
main = (getMenuItem :: IO MainMenu) >> return ()
Run Code Online (Sandbox Code Playgroud)

不幸的是,我遇到了以下错误

• Could not deduce (Menu a0) arising from a use of ‘choices’
  from the context: Menu a
    bound by the type signature for:
               getMenuItem :: Menu a => IO a
    at [removed].hs:15:1-29
  The type variable ‘a0’ is ambiguous
  These potential instance exist:
    instance Menu MainMenu
      -- Defined at [removed].hs:9:10
• In the first argument of ‘putStrLn’, namely ‘choices’
  In a stmt of a 'do' block: putStrLn choices
  In the expression:
    do { putStrLn choices;
         choice <- getLine;
         case parseChoice choice of {
           Just item -> return item
           Nothing -> getMenuItem } }
Run Code Online (Sandbox Code Playgroud)

我知道发生错误是因为Haskell不知道choices使用哪个函数.我尝试了类似putStrLn (choices :: Menu a)但没有成功的事情.

问题是:问题在哪里(以及如何解决)?我应该使用不同的方法(哪个)?

请礼貌,我是Haskell新手.

谢谢.

bhe*_*ilr 6

@porges对于为什么会发生这种情况是正确的,编译器根本没有足够的信息来知道类型类的哪个实例choices将来自哪个.相反,您可以尝试使用幻像类型标记它:

data Choices a = Choices String

class Menu a where
    choices :: Choices a
    parseChoices :: String -> Maybe a
Run Code Online (Sandbox Code Playgroud)

仅这一点还不够,你需要在任何地方使用它来注释choices:

putStrLn (choices :: Choices a)
Run Code Online (Sandbox Code Playgroud)

但这并不是很理想.另一种方法是完全放弃类型类方法并坚持使用基本数据类型:

data Menu a = Menu
    { choices :: String
    , parseChoices :: String -> Maybe a
    }
Run Code Online (Sandbox Code Playgroud)

那你可以做

data MainMenu = FirstItem | SecondItem

mainMenu :: Menu MainMenu
mainMenu = Menu _choices _parseChoices where
    _choices = "1) first choice\n2) second choice"
    _parseChoices "1" = Just FirstItem
    _parsechoices "2" = Just SecondItem
    _parseChoices _   = Nothing
Run Code Online (Sandbox Code Playgroud)

最后

getMenuItem :: Menu a -> IO a
getMenuItem menu@(Menu choices parseChoices) = do
    putStrLn choices
    choice <- getLine
    case parseChoice choice of
        Just item -> return item
        Nothing -> getMenuItem menu

main :: IO ()
main = (getMenuItem mainMenu) >> return ()
Run Code Online (Sandbox Code Playgroud)