wch*_*gin 14 monads haskell monad-transformers
我正在为一门小语言写一本翻译.这种语言支持突变,所以其评估跟踪的Store所有变量(其中type Store = Map.Map Address Value,type Address = Int和data Value是特定于语言的ADT).
计算也可能失败(例如,除以零),因此结果必须是a Either String Value.
那么,我的翻译的类型是
eval :: Environment -> Expression -> State Store (Either String Value)
Run Code Online (Sandbox Code Playgroud)
在哪里type Environment = Map.Map Identifier Address跟踪本地绑定.
例如,解释常量文字不需要触摸商店,结果总是成功,所以
eval _ (LiteralExpression v) = return $ Right v
Run Code Online (Sandbox Code Playgroud)
但是当我们应用二元运算符时,我们确实需要考虑商店.例如,如果用户评估(+ (x <- (+ x 1)) (x <- (+ x 1)))并且x最初是0,则最终结果应该是3,并且x应该2在结果存储中.这导致了这种情况
eval env (BinaryOperator op l r) = do
lval <- eval env l
rval <- eval env r
return $ join $ liftM2 (applyBinop op) lval rval
Run Code Online (Sandbox Code Playgroud)
请注意,do-notation在State Storemonad中工作.此外,使用return单态State Store,而单子的使用join和liftM2单态Either String.也就是说,我们在这里使用
(return . join) :: Either String (Either String Value) -> State Store (Either String Value)
Run Code Online (Sandbox Code Playgroud)
而且return . join不是无操作.
(很明显,applyBinop :: Identifier -> Value -> Value -> Either String Value.)
这似乎令人困惑,这是一个相对简单的案例.例如,功能应用的情况要复杂得多.
我应该知道哪些有用的最佳实践可以保持代码的可读性和可写性?
编辑:这是一个更典型的例子,更好地展示了丑陋.所述NewArrayC变体具有参数length :: Expression和element :: Expression(它创建与初始化为一个恒定的所有元素的给定长度的阵列).一个简单的例子是(newArray 3 "foo"),产生["foo", "foo", "foo"],但我们也可以写(newArray (+ 1 2) (concat "fo" "oo")),因为我们可以在a中有任意表达式NewArrayC.但是当我们真正打电话时
allocateMany :: Int -> Value -> State Store Address,
Run Code Online (Sandbox Code Playgroud)
它获取要分配的元素数量和每个槽的值,并返回起始地址,我们需要解压缩这些值.在下面的逻辑中,你可以看到我复制了一堆应该内置到Eithermonad 的逻辑.所有cases应该只是绑定.
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
Run Code Online (Sandbox Code Playgroud)
Dan*_*ner 13
这就是monad变压器的用途.有一个StateT变换器可以向堆栈添加状态,还有一个EitherT变换器可以Either向堆栈添加类似的故障; 但是,我更喜欢ExceptT(添加Except类似失败),所以我将就此进行讨论.因为你想要有状态位最外面,你应该使用ExceptT e (State s)你的monad.
type DSL = ExceptT String (State Store)
Run Code Online (Sandbox Code Playgroud)
需要注意的是状态操作可以拼写get和put,而这些都是在所有的情况下,多态MonadState; 所以特别是他们会在我们的DSLmonad中工作正常.类似地,引发错误的规范方法是throwError,在所有实例中都是多态的MonadError String; 特别是在我们的DSLmonad中可以正常工作.
所以现在我们会写
eval :: Environment -> Expression -> DSL Value
eval _ (Literal v) = return v
eval e (Binary op l r) = liftM2 (applyBinop op) (eval e l) (eval e r)
Run Code Online (Sandbox Code Playgroud)
您也可以考虑提供eval更多态的类型; 它可以返回(MonadError String m, MonadState Store m) => m Value而不是DSL Value.事实上,因为allocateMany,给它一个多态类型是很重要的:
allocateMany :: MonadState Store m => Int -> Value -> m Address
Run Code Online (Sandbox Code Playgroud)
关于这种类型有两个感兴趣:首先,因为它在所有MonadState Store m实例中都是多态的,所以你可以确定它只有有状态的副作用,就好像它有Int -> Value -> State Store Address你建议的类型一样.但是,也因为它是多态的,它可以专门返回a DSL Address,因此它可以用于(例如)eval.您的示例eval代码变为:
eval env (NewArrayC len el) = do
lenVal <- eval env len
elVal <- eval env el
case lenVal of
NumV lenNum -> allocateMany lenNum elVal
_ -> throwError "expected number in new-array length"
Run Code Online (Sandbox Code Playgroud)
我觉得这很可读,真的; 没有什么太多无关紧要了.
| 归档时间: |
|
| 查看次数: |
239 次 |
| 最近记录: |