【问题标题】:'Unpacking' the data in an SML DataType without a case statement在没有 case 语句的情况下“解包”SML DataType 中的数据
【发布时间】:2016-02-24 20:38:53
【问题描述】:

我有一个 SML 程序,它表示一种语言,其表达式由值组成:

datatype Value = IntVal of int
               | ListVal of Value list

datatype Exp = Const of Value
             | Plus of Exp * Exp
             | Minus of Exp * Exp
             | Times of Exp * Exp

我还在编写一个将表达式转换为值的 eval 函数。如果表达式是 Plus 表达式(例如 Plus (Const (IntVal 1), Const (IntVal 1)) 代表 1+1),我只想取出存储在 IntVal 中的整数并将它们加在一起并返回。

但据我所知,我必须有一个看似多余的 case 语句,只有一个 case 才能获得 IntVal 数据类型中的整数:

(*Evaluates an Exp and returns a Value*)
fun eval e =
  (*Evaluate different types of Exp*)
  case e of
      (*If it's a constant, then just return the Value*)
      Const v => v
      (*If it's a Plus, we want to add together the two Values*)
    | Plus (x,y) =>
         (*Case statement with only one case that seems redundant*)
         case (eval x, eval y) of
            (IntVal xVal, IntVal yVal) => IntVal (xVal + yVal)

有没有简单的方法来简化这个?我想做这样的事情,这当然不是有效的 SML:

fun eval e =
  case e of
      Const v => v
    | Plus (x,y) => IntVal (eval x + eval x)

【问题讨论】:

  • 这对我来说真的没有意义。你的Value 数据类型是递归的——它本质上是一种对应于树的类型,其中节点可以有任意数量的子节点,叶子是整数。将两棵这样的树相加或相乘是什么意思?
  • 我意识到 Value + Value 没有意义,所以我想要一种将这些值转换/处理为 IntVals 的方法,而不需要 case 语句。对我来说,对于单一类型的案例来说,案例陈述似乎太大而沉重
  • @JohnColeman:在为编程语言定义抽象语法时,这种树很常见。你最终得到了一个可以表达无意义事物的类型,一个生成这些无意义树的解析器,最后你在类型检查器中解决了这些无意义的问题。对于决定一段抽象语法是否无意义的语言是一个更复杂的过程,将这个决定放在一个单独的类型检查步骤中被证明是一个有用的结构。

标签: functional-programming sml ml


【解决方案1】:

如果您希望您的 eval 函数返回一个 int 并且您还没有弄清楚如何从使用 Value 构造函数的 int 获取 ListVal 构造函数 - 只需提供与您的预期定义涵盖的情况相对应的模式。

fun eval (Const (IntVal v)) = v
|   eval (Plus (e1,e2)) = eval(e1) + eval(e2)
|   eval (Minus (e1,e2)) = eval(e1) - eval(e2)
|   eval (Times (e1,e2)) = eval(e1) * eval(e2);

SML/NJ 提供 Warning: match nonexhaustive - 但如果它符合您的意图,则您可以忽略警告。

上面的代码返回一个int。如果你想返回看起来像例如的值IntVal 3 然后你可以定义 3 个函数,它们接受一对 IntVals 并返回 IntVals 对应于它们的和、差和乘积,并在上述定义的右侧使用这些函数。

【讨论】:

  • 实际上我认为这是替换错误的案例陈述。我对顶级的没问题,我只是不喜欢只有一个案例的底部。我想我想要的是类似于 valOf 函数,它返回 Option 数据类型中的实际值,如果它是 NONE 则抛出错误。同样,我想要一个返回 Value 类型内的整数的函数,如果它不是 IntVal 则抛出异常。
  • @Miguel fun IntValOf(IntVal v) = v 将返回任何Value 的整数值,它是一个IntVal,但如果你给它一个ListVal 则会引发错误。这不会是自定义错误,但如果您想定义自己的异常,您可以轻松地对其进行修改。您可以定义此函数,然后将我的eval 版本的第一行替换为eval(Const v) = IntValOf(v)
【解决方案2】:

是的,至少有两种方法可以简化这一点:异常或 monad。

您遇到的问题是eval (Const (ListVal [...])) 没有有意义的整数值。为了确保这仍然是一个总函数(所有输入值都产生一个输出值,而不是partial function),它的类型可以改为:

val eval : Exp -> int option

您可以通过使用异常最轻松地实现这一点:

local
  fun eval' (Const (IntVal v)) = v
    | eval' (Const (ListVal _)) = raise Domain
    | eval' (Plus (e1, e2)) = eval' e1 + eval' e2
    | eval' (Minus (e1, e2)) = eval' e1 - eval' e2
    | eval' (Times (e1, e2)) = eval' e1 * eval' e2
in
  fun eval e = SOME (eval' e) handle Domain => NONE
end

或者您可以通过使递归函数复杂化来实现这一点:

fun eval (Const (IntVal v)) = SOME v
  | eval (Const (ListVal _)) = NONE
  | eval (Plus (e1, e2)) =
    (case (eval e1, eval e2) of
          (SOME v1, SOME v2) => SOME (v1+v2)
        | _ => NONE)
  | eval (Minus (e1, e2)) =
    (case (eval e1, eval e2) of
          (SOME v1, SOME v2) => SOME (v1-v2)
       |  _ => NONE)
  | eval (Times (e1, e2)) =
    (case (eval e1, eval e2) of
          (SOME v1, SOME v2) => SOME (v1*v2)
        | _ => NONE)

显然这并不容易或漂亮。

改进此代码的一种方法是将常见的重复模式抽象为函数:

fun evalBinop (f, e1, e2) =
    case (eval e1, eval e2) of
         (SOME v1, SOME v2) => SOME (f (v1, v2))
       | _ => NONE
and eval (Const (IntVal v)) = SOME v
  | eval (Const (ListVal _)) = NONE
  | eval (Plus (e1, e2)) = evalBinop (op+, e1, e2)
  | eval (Minus (e1, e2)) = evalBinop (op-, e1, e2)
  | eval (Times (e1, e2)) = evalBinop (op*, e1, e2)

这里evalBinop 依赖于回调eval,所以我让它们相互递归。我还依赖于将二元运算符解释为以元组作为参数的函数。

一个改进来自于制作更通用的帮助函数来处理 'a 选项 类型:

infix 3 >>=
fun NONE >>= _ = NONE
  | (SOME a) >>= f = f a

fun liftM2 f (opt1, opt2) =
    opt1 >>= (fn x1 => opt2 >>= (fn x2 => SOME (f (x1, x2))))

fun eval (Const (IntVal v)) = SOME v
  | eval (Const (ListVal _)) = NONE
  | eval (Plus (e1, e2)) = liftM2 (op+) (eval e1, eval e2)
  | eval (Minus (e1, e2)) = liftM2 (op-) (eval e1, eval e2)
  | eval (Times (e1, e2)) = liftM2 (op* ) (eval e1, eval e2)

此时,>>=liftM2 是有用的函数,它们不依赖于计算表达式或应用整数运算符的概念。它们不存在于标准 ML 的库中,但它们应该存在。此时我顺便re-invented monads

最后的改进是添加一点语法糖,这很可能是矫枉过正:

infix 7 **
infix 6 ++
infix 6 --
val op** = liftM2 op*
val op++ = liftM2 op+
val op-- = liftM2 op-

fun eval (Const (IntVal v)) = SOME v
  | eval (Const (ListVal _)) = NONE
  | eval (Plus (e1, e2)) = eval e1 ++ eval e2
  | eval (Minus (e1, e2)) = eval e1 -- eval e2
  | eval (Times (e1, e2)) = eval e1 ** eval e2

(几个例子可以准确地说明>>=liftM2 是做什么的......)

(* 'x >>= f' means:
 *     if x is 'NONE', just return NONE
 *     if x is 'SOME a', apply f to a, and expect f to return either 'SOME b' or 'NONE' *)
(* For example, this should give 'SOME 4': *)
val example_1 = SOME 3 >>= (fn x => SOME (x+1))

(* And these should both give 'NONE': *)
val example_2 = NONE >>= (fn x => SOME (x+1))
val example_3 = SOME 3 >>= (fn x => NONE)

(* If 'f : t1 * t2 -> t3', then 'liftM2 f : t1 option * t2 option -> t3 option' *)
val _ = op+ : int * int -> int
val _ = liftM2 op+ : int option * int option -> int option

(* For example *)
val example_4 = liftM2 op+ (SOME 3, SOME 4)  (* gives SOME 7 *)
val example_5 = liftM2 op- (SOME 10, NONE)   (* gives NONE *)
val example_6 = liftM2 op* (NONE, SOME 5)    (* gives NONE *)

【讨论】:

    猜你喜欢
    • 2017-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-19
    • 1970-01-01
    • 2011-08-11
    相关资源
    最近更新 更多