有哪些已知的事情对于其中一个是可能的,而对于另一个则不可行?有哪些已知的惯用语可以解决两者之一的局限性?
\n\n在另一个问题中,Andreas Rossberg 指出了 SML 中的一个限制:它必须采用fn\xe2\x80\x91matchval rec
的形式,即使其他表达式也有意义。
该fun
语法没有这样的限制,但不能用于引入简单的绑定(我的意思是,只是带有可选类型注释的名称,仅此而已),因为它需要公开参数。
在我忘记的一个较旧的问题中,有一些支持或fun
超过val
\xe2\x80\xaf/\xe2\x80\xaf 的离散评论val rec
。
我个人更使用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(* 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
该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
。
归档时间: |
|
查看次数: |
2963 次 |
最近记录: |