2016-12-21 8 views
2

私は、テキストメニューを扱うためのコンソールプログラムを作っています。私は可能なメニューの選択肢の文字列を返す関数choicesと、ユーザによって入力された文字列をメニュー項目に変換する関数parseChoiceを持つクラスMenuを書きました。Haskell - あいまいなクラス関数

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() 

生憎、私はエラー

• 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 } } 

次取得しています私は、Haskellはchoices機能を使用することを知らないため、エラーが発生した知っています。私はputStrLn (choices :: Menu a)のようなものを試しましたが、成功しませんでした。

質問:問題はどこで解決できますか?私は別のアプローチを使うべきですか?

そして、私はハスケル初心者です。

ありがとうございます。

+2

短い答え:typeclassメソッドは引数または戻り値のいずれかの型を持たなければならないので、現在のデザインは 'choices :: String'で動作しません。その理由は、コンパイラは使用されている場所に基づいて型クラスを選択する方法がないからです。あなたは別のデザインを使用する必要がありますが、私は今すぐに思いつく時間がありません:) – porges

答えて

4

@porgesなぜこのようなことが起きるのかについてコンパイラは正確には、どのタイプインスタンスの型ライブラリchoicesが来るのかを知るための十分な情報がありません。代わりに、あなたはファントムの種類とそれをタグ付けしてみてください:

putStrLn (choices :: Choices a) 

この:一人で

data Choices a = Choices String 

class Menu a where 
    choices :: Choices a 
    parseChoices :: String -> Maybe a 

これを非常に十分ではないでしょう、あなたがchoicesを使用し、これまでのタイプに注釈を付ける必要がありますしかし、本当に理想的ではありません。代替が完全に型クラスのアプローチを捨てると、基本的なデータ型に固執することです:

data Menu a = Menu 
    { choices :: String 
    , parseChoices :: String -> Maybe a 
    } 

次に、あなたは問題がラインということです

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 

そして最後に

getMenuItem :: Menu a -> IO a 
getMenuItem [email protected](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() 
3

を行うことができますputStrLn choicesは本質的にあいまいです。 Menuクラスの複数のインスタンスが使用可能な場合は、それらのいずれかを印刷することを意味する場合があります。 Menu a =>で利用可能になったインスタンスを使用するつもりでも構いませんが、別のプログラマはMenu MainMenuインスタンスを選択して、aを無視してください。

1つの選択肢は、型抜きを避けることです。これはおそらく、より健康的で、簡単で、効果的な方法です。ただ、

data Menu = Menu { choices :: String , ... } 

ようMenuタイプを作成し、手動で、周りにその型の値を渡します。我々はいくつかの理由で型クラスに固執したいと仮定すると、

、我々は次のようにchoicesの種類を変更することにより、犯人ラインを明確にすることができます

{-# LANGUAGE ScopedTypeVariables #-} 
import Data.Proxy 

class Menu a where 
    choices :: proxy a -> String 
    ... 

getMenuItem :: forall a. Menu a => IO a 
getMenuItem = do 
    putStrLn (choices (Proxy :: Proxy a)) 
    ... 

追加のプロキシ引数には、ダミーの値を持っています。実行時には情報は保持されませんが、コンパイル時にコンパイラは曖昧さを排除します。1は、この非常に新しいスタイルのオリジナルのもの

{-# LANGUAGE ScopedTypeVariables, AllowAmbiguousTypes, TypeApplications #-} 

class Menu a where 
    choices :: String 
    ... 

getMenuItem :: forall a. Menu a => IO a 
getMenuItem = do 
    putStrLn (choices @ a) 
    ... 

に近いいくつかのコードを使用することができますが、チャンスは、これは将来的に多く使用されることがあるいくつかの他の新しいGHCの拡張子を持つ代わりに

、 。これは、プロキシを周回するよりも簡単なためです。タイプ理論家でさえ明示的な型引数を評価する必要があります。これは多くの型付きラムダ計算でよく見られます。

関連する問題