14

モナド変換子のスタックを作成しようとしていますが、関数の正しい型シグネチャを取得できません。(私はまだHaskellにかなり慣れていません)

スタックには、追跡する必要のある複数の状態(2つは倍増する可能性がありますが、すぐにわかります)とロギング用のWriterTがあるため、複数のStateTトランスフォーマーが組み合わされています。

これが私がこれまでに持っているものです:

module Pass1 where
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map
import Types

data Msg = Error String
         | Warning String

type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a


runPass1 addrs instrs msgs = runIdentity (runStateT (runStateT (runStateT (runWriterT msgs) 1) instrs) addrs)


--popLine :: (MonadState s m) => m (Maybe s)
--popLine :: (Monad m) => StateT [Line] m (Maybe Line)
popLine :: (MonadState s m) => m (Maybe Line)
popLine = do
        ls <- get
        case ls of
          x:xs -> do
                    put xs
                    return $ Just x
          []   -> return Nothing


incLineNum :: (Num s, MonadState s m) => m ()
incLineNum = do
               ln <- get
               put $ ln + 1

curLineNum :: (MonadState s m) => m s
curLineNum = do
               ln <- get
               return ln

evalr = do l <- popLine
           --incLineNum
           return l

状態や機能をpopLineいじって状態に影響を与えてほしい。に渡される計算です。[Line]xLineNumIntevalrrunPass1

コードをロードするたびに、一般的に次のようなエラーが発生します。

Pass1.hs:23:14:
    No instance for (MonadState [t] m)
      arising from a use of `get' at Pass1.hs:23:14-16
    Possible fix: add an instance declaration for (MonadState [t] m)
    In a stmt of a 'do' expression: ls <- get
    In the expression:
        do ls <- get
           case ls of {
             x : xs -> do ...
             [] -> return Nothing }
    In the definition of `popLine':
        popLine = do ls <- get
                     case ls of {
                       x : xs -> ...
                       [] -> return Nothing }


Pass1.hs:22:0:
    Couldn't match expected type `s' against inferred type `[Line]'
      `s' is a rigid type variable bound by                        
          the type signature for `popLine' at Pass1.hs:21:23        
    When using functional dependencies to combine                  
      MonadState [Line] m,                                         
        arising from a use of `get' at Pass1.hs:23:14-16            
      MonadState s m,                                              
        arising from the type signature for `popLine'              
                     at Pass1.hs:(22,0)-(28,31)                     
    When generalising the type(s) for `popLine'         




Pass1.hs:23:14:
    Could not deduce (MonadState [Line] m)
      from the context (MonadState s m)   
      arising from a use of `get' at Pass1.hs:23:14-16
    Possible fix:                                    
      add (MonadState [Line] m) to the context of    
        the type signature for `popLine'             
      or add an instance declaration for (MonadState [Line] m)
    In a stmt of a 'do' expression: ls <- get
    In the expression:
        do ls <- get
           case ls of {
             x : xs -> do ...
             [] -> return Nothing }
    In the definition of `popLine':
        popLine = do ls <- get
                     case ls of {
                       x : xs -> ...
                       [] -> return Nothing }

どの署名も正しくないようですが、popLineは最初の関数であるため、すぐにエラーが発生するのはpopLineだけです。

私はそれが型署名に示唆するものを追加しようとします(例えば:popLine :: (MonadState [Line] m) => ...しかしそれからそれは次のようにエラーになります:

Pass1.hs:21:0:
    Non type-variable argument in the constraint: MonadState [Line] m
    (Use -XFlexibleContexts to permit this)                          
    In the type signature for `popLine':                             
      popLine :: (MonadState [Line] m) => m (Maybe Line)

型変数ではないことをしようとすると、常にこのメッセージが表示されるようです。(MonadState s m)他の何かでOKとエラーが好きなようですが、[a]代わりにsそれを試してみると、上記のようなエラーが発生します。(最初は[Line]とIntが単一の状態でタプリングされていましたが、このエラーが発生していたので、別々の状態にしようと思いました)。

GHC 6.10.4、Kubuntu

だから、誰かが私に何が起こっているのかを教えて説明を与える/正しい型の署名を見せてくれるか、または誰かがこのようなものに関する良いリファレンスを知っていますか(これまでに役立ったのは「モナド変換子のステップバイステップ」だけでした) 、しかしそれはただ1つのaux状態関数と1つのStateTを使用しますか?

よろしくお願いします。

編集
これは、JFTとEdwardの提案を組み込んだコンパイルコードです。

{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- needed for: deriving (Functor,Monad)
{-# LANGUAGE MultiParamTypeClasses #-}      -- needed for: MonadState instance
{-# LANGUAGE FlexibleContexts #-}           -- needed for: (MonadState PassState m) => ...

module Pass1 where
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import Types

type Lines     = [Line]
type Addresses = [Address]
type LineNum   = Int
type Messages  = [Msg]
data Msg = Error String
         | Warning String

data PassState = PassState { passLineNum :: LineNum
                           , passLines :: Lines
                           , passAddresses :: Addresses
                           }

newtype Pass1 a = Pass1 { unPass1 :: WriterT Messages (State PassState) a
                        }
                        deriving (Functor,Monad)

instance MonadState PassState Pass1 where
        get   = Pass1 . lift $ get
        put s = Pass1 . lift $ put s



runPass1 :: PassState -> Pass1 a -> ((a, Messages), PassState)
runPass1 state = flip runState state .
                 runWriterT          .
                 unPass1


curLineNum :: (MonadState PassState m) => m LineNum
curLineNum = do
               state <- get
               return $ passLineNum state


nextLine :: (MonadState PassState m) => m (Maybe Line)
nextLine = do
             state <- get
             let c = passLineNum state
             let l = passLines state
             case l of
               x:xs -> do
                         put state { passLines = xs, passLineNum = (c+1) }
                         return $ Just x
               _ -> return Nothing



evalr :: Pass1 (Maybe Line,LineNum)
evalr = do
          l <- nextLine
          c <- curLineNum
          --tell $ Warning "hello"
          return (l,c)

私は結合incLineNumし、ライターモナド部分を機能popLinenextLineせる必要がありますが、ここからどこに行くべきかはわかっていると思います。みんなありがとう。

4

2 に答える 2

40

コード スニペットには多くの問題がありました。何が壊れているかについての説明を追加してスニペットを修正し、気になる場合はスタイルのアドバイスを追加しました。

module Pass1_JFT where
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map

{- インポート タイプを単純な定義に置き換えます -}

--import Types
type Line       = String
type Address    = String
type LineNumber = Int

{- 質問の一部ではありませんが、ここで 2 セント... タイプ エイリアスを使用しない場合、州のコレクションを変更したいとします。代わりに、必要に応じてこれらの定義を変更してください -}

type Lines     = [Line]
type Addresses = [Address]
type Messages  = [Msg]


data Msg = Error String
         | Warning String

{- StateT Int の Int とは何ですか? 読みやすく、推論しやすく、変更しやすい名前を付けます。宣言型 FTW 代わりに LineNumber を使用しましょう -}

--type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a

{- インスタンスを派生できるように、「実際の」型を使用しましょう。Pass1 はモナド転送ではない、つまり Pass1 ma として定義されていないため、最も深い StateT ie StateT [Address] Identity に StateT を使用しても意味がないので、State [Address] -} を使用しましょう。

newtype Pass1 a = Pass1 {
    unPass1 :: WriterT Messages (StateT LineNumber (StateT Lines (State Addresses))) a
                        }
                        deriving (Functor,Monad)

--runIdentity (runStateT (runStateT (runStateT (runWriterT msgs) 1) instrs) addrs)

{- 一番外側 (宣言の一番左) から一番内側までのスタックをはがしてみましょう。元の宣言の Identity でした。runWriterT は開始状態を取らないことに注意してください... runStateT (および runState) の最初のパラメーターは初期状態ではなくモナドです... では、反転しましょう! -}

runPass1' :: Addresses -> Lines -> Messages -> Pass1 a ->  ((((a, Messages), LineNumber), Lines), Addresses)
runPass1' addrs instrs msgs = flip runState addrs   .
                              flip runStateT instrs .
                              flip runStateT 1      .
                              runWriterT            . -- then get process the WriterT (the second outermost)
                              unPass1                 -- let's peel the outside Pass1

{- WriterT に追加する初期ログを提供する必要があるため、最後の関数は目的の処理を実行しません。これはモナド変換子なので、ここでいくつかのトリックを行います -}

-- I keep the runStateT convention for the order of the arguments: Monad then state
runWriterT' :: (Monad m,Monoid w) => WriterT w m a -> w -> m (a,w)
runWriterT' writer log = do
    (result,log') <- runWriterT writer
    -- let's use the monoid generic append in case you change container...
    return (result,log `mappend` log')

runPass1 :: Addresses -> Lines -> Messages -> Pass1 a ->  ((((a, Messages), LineNumber), Lines), Addresses)
runPass1 addrs instrs msgs = flip runState addrs   .
                             flip runStateT instrs .
                             flip runStateT 1      .
                             flip runWriterT' msgs . -- then get process the WriterT (the second outermost)
                             unPass1                 -- let's peel the outside Pass1

{- Pass1 スタックから直接 popLine を呼び出すつもりですか? その場合は、Pass1 を「MonadState Lines」になるように「教える」必要があります。そのために、Pass1 を派生させましょう (これが、newtype で宣言した理由です!) -}

instance MonadState Lines Pass1 where
    -- we need to dig inside the stack and "lift" the proper get
    get   = Pass1 . lift . lift $ get
    put s = Pass1 . lift . lift $ put s

{- 一般的なものにしておくほうがよいですが、次のように書くこともできます: popLine :: Pass1 (Maybe Line) -}

popLine :: (MonadState Lines m) => m (Maybe Line)
popLine = do
        ls <- get
        case ls of
          x:xs -> do
                    put xs
                    return $ Just x
          []   -> return Nothing

{- わかりました、Int => LineNumber を取得します.... Pass1 と MonadState LineNumber のインスタンスを作成できますが、LineNumber をいじってはいけません。代わりに、incLine を直接コーディングし、必要に応じてコンサルテーション用に MonadReader インスタンスを提供します。

check ":t incLineNum and :t curLineNum"

-}

incLineNum = Pass1 . lift $ modify (+1)

curLineNum = Pass1 $ lift get

evalr = do l <- popLine
           incLineNum
           return l

長く曲がりくねった応答ですが、ご覧のようにモナドとモナドスタックは最初は挑戦的です。コードを修正しましたが、何が起こっているのかを理解し、オリジナルと比較するために、さまざまな関数の型を再生して調べることをお勧めします。Haskell の型推論は、通常、型注釈は不要であることを意味します (あいまいさを除去しない限り)。一般に、関数に与える型は一般的ではないため、型に注釈を付けないほうがよいでしょう。ただし、型注釈は間違いなく優れたデバッグ手法です;)

乾杯

Monad Transformer に関する PS Real World Haskell の章は優れています: http://book.realworldhaskell.org/read/monad-transformers.html

于 2010-01-18T02:52:24.623 に答える
12

一般に、必要な状態のすべてのビットに対して、より大きな複合構造を持つ 1 つの StateT を使用すると、コードがより明確になることがわかります。正当な理由の 1 つは、忘れていた状態の断片を思いついたときに、いつでも構造を 1 フィールドずつ拡張できることです。レコード シュガーを使用して、単一のフィールドの更新を書き出すか、fclabels や data-accessor のようなものを使用できます。状態を操作するパッケージ。

data PassState = PassState { passLine :: Int, passLines :: [Line] }

popLine :: MonadState PassState m => m (Maybe Line).   
popLine = do
   state <- get
   case passLines state of
      x:xs -> do 
         put state { passLines = xs }
         return (Just x)
      _ -> return Nothing
于 2010-01-18T11:48:29.200 に答える