私は小さな言語のための通訳を書いています。 この言語は突然変異をサポートしているため、評価者はすべての変数(type Store = Map.Map Address Value
,type Address = Int
、およびdata Value
が言語固有のADTである)のStore
を追跡します。ネストされたモナドではどうすればきれいに作業できますか?
計算が失敗する可能性もあります(ゼロで割るなど)。結果はEither String Value
である必要があります。
私の通訳の種類は、その後、type Environment = Map.Map Identifier Address
はローカルな束縛を追跡
eval :: Environment -> Expression -> State Store (Either String Value)
です。例えば
、定数リテラルを解釈することは店をタッチする必要があり、結果は常に成功し、そう
eval _ (LiteralExpression v) = return $ Right v
しかし、我々は二項演算子を適用すると、私たちは店を検討する必要もありません。 たとえば、ユーザーが(+ (x <- (+ x 1)) (x <- (+ x 1)))
およびx
を最初に評価する場合、最終結果は3
であり、結果のストアではx
はである必要があります。 これはdo
-notationがState Store
モナド内で作業されている場合
eval env (BinaryOperator op l r) = do
lval <- eval env l
rval <- eval env r
return $ join $ liftM2 (applyBinop op) lval rval
注意につながります。 さらに、return
の使用はState Store
では単形であり、join
およびliftM2
の使用はEither String
モナドでは単形である。 、ここで私たちは
(return . join) :: Either String (Either String Value) -> State Store (Either String Value)
とreturn . join
を使用する無操作ではありませんされていない 。
(明らかなように、applyBinop :: Identifier -> Value -> Value -> Either String Value
。)
これが最高の状態で混乱と思われるが、これは比較的単純なケースです。 たとえば、関数アプリケーションの場合はかなり複雑です。
私のコードを読み書き可能な状態に保つために知っておくべき有用なベストプラクティスはありますか?
EDIT:ここでは、より典型的な例を紹介します。これは、醜さをよりよく示します。 NewArrayC
バリアントは、パラメータlength :: Expression
とelement :: Expression
(すべての要素を定数に初期化して所定の長さの配列を作成します)を持ちます。 単純な例はで、["foo", "foo", "foo"]
が得られますが、NewArrayC
に任意の式を入れることができるので、(newArray (+ 1 2) (concat "fo" "oo"))
と書くこともできます。 しかし、我々は実際に割り当てる要素の数と各スロットの値をとり、開始アドレスを返し
allocateMany :: Int -> Value -> State Store Address,
を呼び出すとき、我々は、これらの値を展開する必要があります。 以下のロジックでは、Either
モナドに組み込む必要があるロジックの束を複製していることがわかります。 case
はすべてバインドする必要があります。
eval env (NewArrayC len el) = do
lenVal <- eval env len
elVal <- eval env el
case lenVal of
Right (NumV lenNum) -> case elVal of
Right val -> do
addr <- allocateMany lenNum val
return $ Right $ ArrayV addr lenNum -- result data type
left -> return left
Right _ -> return $ Left "expected number in new-array length"
left -> return left
これは素晴らしいです。ありがとうございました。 – wchargin
あなたが正しいと思うのは、ステートフルなビットを最外にしたいということです(特に、これは識別子の参照が状態によって失敗する可能性があるためです)。しかし、 'ExceptT String(State Store) StateT Store(いずれかの文字列)がこれを実行します。実際、[この投稿は別のことを示唆しているようです](http://stackoverflow.com/a/5076096/732016)。説明していただけますか? – wchargin
あなたは何かをベースで取得するまで、newtypesを拡張し続けるだけです。 'ExceptionT String(State Store)a'は' Store->(Store、Either String a) 'に展開され、' StateT Store(Stringを除く)は 'Store' - >どちらの文字列にも展開されます(Store、a) 'とする。あなたが望むものは、もちろんあなた次第です。 –