SML语法:`val rec`和`fun`相互比较

时间:2014-09-17 01:26:41

标签: syntax sml ml

对一个人而不是另一个人有什么可能的知识?解决两者中任何一个限制的已知成语是什么?

我所知道的

another question中,Andreas Rossberg指出在SML中适用于val rec的限制:它必须是 fn-match 的形式,即使其他表达式也是如此有道理。

fun语法没有这样的限制,但不能用于引入简单的绑定(我的意思是,只是一个带有可选类型注释的名称而不是其他内容),因为它需要参数暴露。

在一个较旧的问题中,我忽略了,有偏好的评论赞成或fun超过val / val rec

我个人更多地使用val / val rec,因为它揭示了自递归和非自递归绑定之间的区别(而自我递归暴露的可能实际上并不存在,另一个总是持有,暴露为非自递归的东西永远不会自我递归),也因为它使用与匿名lambda表达式相同的语法(更一致)。

(所有相关的)问题

这些是我所知道的。还有其他人吗?我不太了解任何解决方法的习语。他们是一些吗?

两者的限制在我看来只是语法上的,并没有真正的语义或健全背景。对于这些限制,这确实还是存在语义和健全背景?

示例案例(您可以跳过它)

如果它没有滥用,我会在一个片段下面发帖,这是上面链接的问题中发布的片段的变体。这个片段揭示了我遇到两个问题的情况(我对两者都不满意)。这些评论告诉我这两个问题在哪里,以及为什么这个问题在我看来。这个样本不能真正简化,因为这个问题是语法上的,所以真正的用例很重要。

(* ======================================================================== *)

(* A process chain. *)

datatype 'a process = Chain of ('a -> 'a process)

(* ------------------------------------------------------------------------ *)

(* An example controlling iterator using a process chain. it ends up to be
 * a kind of co‑iteration (if that's not misusing the word). *)

val rec iter =
   fn process: int -> int process =>
   fn first: int =>
   fn last: int =>
      let
         val rec step =
            fn (i, Chain process) =>
               if i < first then ()
               else if i = last then (process i; ())
               else if i > last then ()
               else
                  let val Chain process = process i
                  in step (i + 1, Chain process)
                  end
      in step (first, Chain process)
      end

(* ------------------------------------------------------------------------ *)

(* A tiny test use case. *)

val rec process: int -> int process =
   fn a: int =>
     (print (Int.toString a);
      Chain (fn a => (print "-";
      Chain (fn a => (print (Int.toString a);
      Chain (fn a => (print "|";
      Chain process)))))))

(* Note the above is recursive: fn x => (a x; Chain (fn x => …)). We can't
 * easily extract seperated `fn`, which would be nice to help composition.
 * This is solved in the next section. *)

val () = iter process 0 20
val () = print "\n"

(* ======================================================================== *)

(* This section attempts to set‑up functions and operators to help write
 * `process` in more pleasant way or with a more pleasant look (helps
 * readability).
 *)

(* ------------------------------------------------------------------------ *)

(* Make nested functions, parameters, with an helper function. *)

val chain: ('a -> unit) -> ('a -> 'a process) -> ('a -> 'a process) =
   fn e =>
   fn p =>
   fn a => (e a; Chain p)

(* Now that we can extract the nested functions, we can rewrite: *)

val rec process: int -> int process =
   fn a =>
      let
         val e1 = fn a => print (Int.toString a)
         val e2 = fn a => print "-"
         val e3 = fn a => print (Int.toString a)
         val e4 = fn a => print "|"
      in
         (chain e1 (chain e2 (chain e3 (chain e4 process)))) a
      end

(* Using this:
 *     val e1 = fn a => print (Int.toString a)
 *     val e2 = fn a => print "-"
 *     …
 *
 * Due to an SML syntactical restriction, we can't write this:
 *     val rec process = chain e1 (chain e2 ( … process))
 *
 * This requires to add a parameter on both side, but this, is OK:
 *     fun process a = (chain e1 (chain e2 ( … process))) a
 *)

val e1 = fn a => print (Int.toString a)
val e2 = fn a => print "-"
val e3 = fn a => print (Int.toString a)
val e4 = fn a => print "|"

(* An unfortunate consequence of the need to use `fun`: the parameter added
 * for `fun`, syntactically appears at the end of the expression, while it
 * will be the parameter passed to `e1`. This syntactical distance acts
 * against readability.
 *)

fun process a = (chain e1 (chain e2 (chain e3 (chain e4 process)))) a

(* Or else, this, not better, with a useless `fn` wrapper: *)

val rec process = fn a =>
   (chain e1 (chain e2 (chain e3 (chain e4 process)))) a

(* A purely syntactical function, to move the last argument to the front. *)

val start: 'a -> ('a -> 'b) -> 'b = fn a => fn f => f a

(* Now that we can write `start a f` instead of `f a`, we can write: *)

fun process a = start a (chain e1 (chain e2 (chain e3 (chain e4 process))))

infixr 0 THEN
val op THEN = fn (e, p) => (chain e p)

fun process a = start a (e1 THEN e2 THEN e3 THEN e4 THEN process)

(* This is already more pleasant (while still not perfect). Let's test it: *)

val () = iter process 0 20
val () = print "\n"

2 个答案:

答案 0 :(得分:3)

val rec表单计算最小的固定点。在一般情况下(至少不是严格的语言),这样的定点并不总是定义明确的或唯一的。特别是,如果右侧包含需要进行非平凡计算的表达式,那么递归绑定的含义应该是什么呢?这些计算已经取决于定义了什么?

没有有用的答案,因此SML(像许多其他语言一样)限制递归到(句法)函数。这样,它就像Y这样众所周知的固定点运算符有明确的语义解释,并且可以给出足够简单的评估规则。

当然,这同样适用于fun。更具体地说,

fun f x y = e

仅被定义为

的语法糖
val rec f = fn x => fn y => e

因此fun必须至少有一个参数才能满足val rec的句法要求。

答案 1 :(得分:1)

我会尝试回答我自己的问题。

对于由于语法限制而强制使用包装器fn的情况(可能是考虑使用sML进行解决的问题?),我可以找到,而不是真正的解决方法,但是成语有助于减少这些案件的噪音。

我重复使用示例中的start函数(请参阅问题),并将其重命名为n_equiv,原因在评论中给出。这只需要一些先前的措辞来解释η等价是什么,并且还要说明合理定义和使用这个函数的语法限制(无论如何这总是有利于学习材料,我打算发布法国论坛上的一些SML样本。)

(* A purely syntactical function, to try to make forced use of `fn` wrappers
 * a bit more acceptable. The function is named `n_equiv`, which refers to
 * the η-equivalence axiom. It explicitly tells the construction has no
 * effect. The function syntactically swap the function expression and its
 * argument, so that both occurrences of the arguments appears close
 * to each other in text, which helps avoid disturbance. 
 *)

val n_equiv: 'a -> ('a -> 'b) -> 'b = fn a => fn f => f a

问题中的示例中的用例,现在看起来像这样:

fun process a = n_equiv a (chain e1 (chain e2 (chain e3 (chain e4 process))))
…
fun process a = n_equiv a (e1 THEN e2 THEN e3 THEN e4 THEN process)

这已经更好了,因为现在人们清楚地告诉周围的结构是中立的。

要回答问题的其他部分,至少使用fun而不是使用val rec来处理此案例,与val rec一样,n_equiv自我记录成语无法应用。这是fun超过val rec … = fn …

的观点

更新#1

提及funval的比较详情的页面:TipsForWritingConciseSML (mlton.org)。请参阅页面中间的“Clausal函数定义”。对于非自递归函数,val … fnfun更简洁,对于自递归函数可能会有所不同。