如何为以下数据类型定义monad实例?

这是我必须使用的代码:

infixl 9 :@: -- This is newly defined symbol used in the application of expressions

data Expr
  =  Lit Integer      -- a literal
  |  Var String       -- a variable
  |  Bin Expr Op Expr -- binary operator
  |  Abs String Expr  -- a lambda expression
  |  Expr :@: Expr    -- an application
  deriving Show

data Op = Plus | Mult
  deriving Show

data Value = IntVal Integer | FunVal Env String Expr
  deriving Show

type Env = [(String,Value)]

applyIntOp :: Op -> Value -> Value -> Value
applyIntOp op (IntVal v1) (IntVal v2) = 
   case op of 
      Plus -> IntVal (v1 + v2)
      Mult -> IntVal (v1 * v2)

eval :: Expr -> Env -> Value
eval (Lit i)        env = IntVal i
eval (Var v)        env = fromJust (lookup v env)
eval (Bin e1 op e2) env = applyIntOp op (eval e1 env) (eval e2 env)
eval (Abs v b)      env = FunVal env v b
eval (ef :@: ea)    env = let FunVal env var body = eval ef env
                              arg                 = eval ea env
                           in eval body ((var,arg):env) 

目标是制作一个评估函数,使其可以使用变量(变量字符串),其中变量取自环境(Env)。但是,当我尝试使用这些类型中的任何一种来定义monad时,我不能这样做,因为它们的类型不正确(*代替*-> *)。

因此,我将如何定义此Monad,以便可以使用它正确地评估任何表达式。

instance Monad ? where
  return = ..
  >>=    = .. 

例如(3 +(x * 4)(apply)(x = 1 + 2)):

eval (Bin (Lit 3) Plus ((Abs "x" (Bin (Var "x") Mult (Lit 4))) :@: Bin (Lit 1) Plus (Lit 2))) []

这将返回15

skycom11 回答:如何为以下数据类型定义monad实例?

请注意,您的eval (ef :@: ae)情况是错误的,因为在评估envef时使用了错误的ea副本。您要改为:

eval (ef :@: ea)    env = let FunVal env' var body = eval ef env
                              arg                  = eval ea env
                           in eval body ((var,arg):env')

无论如何,让我们开始讨论为eval之类的评估函数“使用monad”的常用方法。

您的eval函数具有签名:

eval :: Expr -> Env -> Value

即使它的主要用途是获取Expr并确定其Value(即,它基本上是一个函数Expr -> Value),您还是需要一些额外的东西您需要携带的信息,即描述变量绑定的Env。即使您仅在处理VarSub构造函数时访问这些绑定,并且仅在处理:@:构造函数时修改这些绑定,您仍然需要在所有情况下都传递Env无关紧要的,尤其是像这样的情况:

eval (Bin e1 op e2) env = applyIntOp op (eval e1 env) (eval e2 env)

甚至没有接触 env,但需要将其传递给每个递归eval调用。

为避免这种情况,人们通常要做的是引入单子语环境进行评估。 不涉及为ExprEnvValue编写monad实例。相反,它涉及将eval的签名更改为接受Expr并返回Value,但是在“ monadic上下文”中可以使Env可用于检查和修改:

evalM :: Expr -> MyMonad Value

实际上有一个现成的monad,所以您不必自己编写:

import Control.Monad.Reader
type MyMonad = Reader Env

在此之后,编写不关心Env的案例会稍微容易一些:

evalM (Lit i) = pure $ IntVal i
evalM (Bin e1 op e2) = applyIntOp op <*> eval e1 <*> eval e2

要做的在乎Env的情况看起来也不错:

evalM (Var v) = asks (fromJust . lookup v)
evalM (Abs v b) = FunVal <$> ask <*> pure v <*> pure b
evalM (ef :@: ea) = join $ apply <$> evalM ef <*> evalM ea
  where apply (FunVal env var body) va =
          local (const ((var,va):env)) $ evalM body

诚然,在此示例中引入MyMonad并没有太大帮助,但是如果您决定扩展语言以支持可变值,错误处理和IO副作用,那么您将开始看到更多的优势这种方法。

再次,这是使用单子进行评估的通常方式,但是我不确定这是否是您要的。

使用monad进行评估的不寻常方式是为Expr类型编写monad实例,其中monadic动作是某种“替代”。很难想象这将如何工作。最明显的出发点是考虑在不更改整体结构的情况下通常使用monad递归替换结构元素的方式。例如,可以将list monad视为将列表的每个元素替换为新列表,但是与其返回列表列表,不如将列表全部串联在一起,使其具有与原点相同的类型。对于叶子处具有值的树,通常有一个monad可以用关联的树替换每片叶子,但是结构不是平坦的,而是给出了与原始树相同类型的树。 >

从这个起点开始,很容易想像以叶子为变量引用的表达式替换monad:

data ExprM a
  =  LitM Integer
  |  VarM a
  |  BinM (ExprM a) Op (ExprM a)
  deriving (Show,Functor)

单子动作可以用VarM a(树)替换每个ExprM a(叶):

instance Applicative ExprM where
  pure = VarM
  (<*>) = ap
instance Monad ExprM where
  VarM x >>= f = f x
  LitM n >>= f = LitM n
  BinM e1 op e2 >>= f = BinM (e1 >>= f) op (e2 >>= f)

请注意,没有AppM构造函数,因为它已被monadic绑定代替。如果要用LitM 1替换表达式VarM "x"BinM (VarM "x") Plus (VarM "x")的值,您可以这样做:

body1 = BinM (VarM "x") Plus (VarM "x")

context1 "x" = LitM 1
context1 _ = error "variable not found"

example1 = body1 >>= context1

给出:

> example1
BinM (LitM 1) Plus (LitM 1)

问题在于,尚不清楚如何重新添加Abs构造函数。它实际上不能具有形式:

... | Abs a (ExprM a) | ...

因为唯一明智的monad实例将不得不对参数名称进行替换,这没有任何意义。它也容易被变量捕获。

如果Abs有一些巧妙的表示形式,让您坚持采用单子替换方案,那么我看不到它。

本文链接:https://www.f2er.com/2997804.html

大家都在问