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

Hib*_*u57 2 syntax ml sml

有哪些已知的事情对于其中一个是可能的,而对于另一个则不可行?有哪些已知的惯用语可以解决两者之一的局限性?

\n\n

我所知道的

\n\n

另一个问题中,Andreas Rossberg 指出了 SML 中的一个限制:它必须采用fn\xe2\x80\x91matchval rec的形式,即使其他表达式也有意义。

\n\n

fun语法没有这样的限制,但不能用于引入简单的绑定(我的意思是,只是带有可选类型注释的名称,仅此而已),因为它需要公开参数。

\n\n

在我忘记的一个较旧的问题中,有一些支持或fun超过val\xe2\x80\xaf/\xe2\x80\xaf 的离散评论val rec

\n\n

我个人更使用val\xe2\x80\xaf/\xe2\x80\xaf val rec,因为它暴露了 self\xe2\x80\x91 递归和非 \xe2\x80\x91self\xe2\x80\x91 递归绑定之间的区别(而什么\暴露为 self\xe2\x80\x91recursive 实际上可能不是,另一种方式总是成立,暴露为非 self\xe2\x80\x91recursive 的东西永远不会 self\xe2\x80\x91recursive),而且还因为它使用与匿名 lambda 表达式相同的语法(更加一致)。

\n\n

(所有相关的)问题

\n\n

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

\n\n

在我看来,两者的局限性似乎只是语法上的,没有真正的语义或健全性背景。这确实是这样吗?或者这些限制是否有语义和合理性背景?

\n\n

示例案例(可以跳过)

\n\n

如果不是滥用,我将在下面发布一个片段,这是上面链接的问题中发布的片段的变体。这段代码暴露了我对两者都有问题的情况(我对两者都不满意)。评论告诉我们两个问题在哪里以及为什么在我看来这是问题。该示例无法真正简化,因为问题是语法问题,因此真正的用例很重要。

\n\n
(* ======================================================================== *)\n\n(* A process chain. *)\n\ndatatype \'a process = Chain of (\'a -> \'a process)\n\n(* ------------------------------------------------------------------------ *)\n\n(* An example controlling iterator using a process chain. it ends up to be\n * a kind of co\xe2\x80\x91iteration (if that\'s not misusing the word). *)\n\nval rec iter =\n   fn process: int -> int process =>\n   fn first: int =>\n   fn last: int =>\n      let\n         val rec step =\n            fn (i, Chain process) =>\n               if i < first then ()\n               else if i = last then (process i; ())\n               else if i > last then ()\n               else\n                  let val Chain process = process i\n                  in step (i + 1, Chain process)\n                  end\n      in step (first, Chain process)\n      end\n\n(* ------------------------------------------------------------------------ *)\n\n(* A tiny test use case. *)\n\nval rec process: int -> int process =\n   fn a: int =>\n     (print (Int.toString a);\n      Chain (fn a => (print "-";\n      Chain (fn a => (print (Int.toString a);\n      Chain (fn a => (print "|";\n      Chain process)))))))\n\n(* Note the above is recursive: fn x => (a x; Chain (fn x => \xe2\x80\xa6)). We can\'t\n * easily extract seperated `fn`, which would be nice to help composition.\n * This is solved in the next section. *)\n\nval () = iter process 0 20\nval () = print "\\n"\n\n(* ======================================================================== *)\n\n(* This section attempts to set\xe2\x80\x91up functions and operators to help write\n * `process` in more pleasant way or with a more pleasant look (helps\n * readability).\n *)\n\n(* ------------------------------------------------------------------------ *)\n\n(* Make nested functions, parameters, with an helper function. *)\n\nval chain: (\'a -> unit) -> (\'a -> \'a process) -> (\'a -> \'a process) =\n   fn e =>\n   fn p =>\n   fn a => (e a; Chain p)\n\n(* Now that we can extract the nested functions, we can rewrite: *)\n\nval rec process: int -> int process =\n   fn a =>\n      let\n         val e1 = fn a => print (Int.toString a)\n         val e2 = fn a => print "-"\n         val e3 = fn a => print (Int.toString a)\n         val e4 = fn a => print "|"\n      in\n         (chain e1 (chain e2 (chain e3 (chain e4 process)))) a\n      end\n\n(* Using this:\n *     val e1 = fn a => print (Int.toString a)\n *     val e2 = fn a => print "-"\n *     \xe2\x80\xa6\n *\n * Due to an SML syntactical restriction, we can\'t write this:\n *     val rec process = chain e1 (chain e2 ( \xe2\x80\xa6 process))\n *\n * This requires to add a parameter on both side, but this, is OK:\n *     fun process a = (chain e1 (chain e2 ( \xe2\x80\xa6 process))) a\n *)\n\nval e1 = fn a => print (Int.toString a)\nval e2 = fn a => print "-"\nval e3 = fn a => print (Int.toString a)\nval e4 = fn a => print "|"\n\n(* An unfortunate consequence of the need to use `fun`: the parameter added\n * for `fun`, syntactically appears at the end of the expression, while it\n * will be the parameter passed to `e1`. This syntactical distance acts\n * against readability.\n *)\n\nfun process a = (chain e1 (chain e2 (chain e3 (chain e4 process)))) a\n\n(* Or else, this, not better, with a useless `fn` wrapper: *)\n\nval rec process = fn a =>\n   (chain e1 (chain e2 (chain e3 (chain e4 process)))) a\n\n(* A purely syntactical function, to move the last argument to the front. *)\n\nval start: \'a -> (\'a -> \'b) -> \'b = fn a => fn f => f a\n\n(* Now that we can write `start a f` instead of `f a`, we can write: *)\n\nfun process a = start a (chain e1 (chain e2 (chain e3 (chain e4 process))))\n\ninfixr 0 THEN\nval op THEN = fn (e, p) => (chain e p)\n\nfun process a = start a (e1 THEN e2 THEN e3 THEN e4 THEN process)\n\n(* This is already more pleasant (while still not perfect). Let\'s test it: *)\n\nval () = iter process 0 20\nval () = print "\\n"\n
Run Code Online (Sandbox Code Playgroud)\n

And*_*erg 5

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

不存在有用的答案,因此 SML(像许多其他语言一样)将递归限制为(语法)函数。这样,它对于像 Y 这样的众所周知的不动点算子就有了清晰的语义解释,并且可以给出足够简单的评估规则。

fun当然,这同样适用于。进一步来说,

fun f x y = e
Run Code Online (Sandbox Code Playgroud)

仅仅被定义为语法糖

val rec f = fn x => fn y => e
Run Code Online (Sandbox Code Playgroud)

因此必须至少有一个参数来fun满足 的语法要求val rec