SML/NJ - 使用 foldr 的一种线长函数

Posted

技术标签:

【中文标题】SML/NJ - 使用 foldr 的一种线长函数【英文标题】:SML/NJ - One line length function using foldr 【发布时间】:2016-02-02 16:41:33 【问题描述】:

我正在尝试创建一个长度函数,类似于 ML 中已经包含的函数。我的限制是它必须在一行上完成并使用 map、foldl 或 foldr。

现在我的代码行如下所示:

val mylength = foldr ( fn(x,y) => 1+y) 0;

我绝不是机器学习方面的专家,但到目前为止我的推理是这样的:

据我了解,foldr 将从列表中的最后一项开始,将其作为 x 参数传递给我的函数,并使用 0 作为初始 y 值。然后它应该在 y 值上加 1 并且基本上忽略 x。理论上,我相信这会给我我的总长度。但是我收到以下错误:

 stdIn:136.5-136.37 Warning: type vars not generalized because of
   value restriction are instantiated to dummy types (X1,X2,...)
 val mylength = fn : ?.X1 list -> int

我的大问题是弄清楚如何以一种可以接受任何类型列表的方式创建这个函数。

如果有人能就如何解决这个问题提供一些建议,我将不胜感激,也许我还没有完全理解 ML 的编程风格。

【问题讨论】:

【参考方案1】:

您的功能本质上是正确的。根据您使用的解释器,它将接受或拒绝给定的代码。例如,在CloudML 上运行您的代码就可以了。 为了避免这个问题,请将其定义为这样的函数:

fun mylength l = foldr ( fn(x,y) => 1+y) 0 l;

华盛顿大学的 Daniel Grossman 在他的一堂课中解释说,这个错误与可变引用有关。很遗憾,我不记得他在哪节课中提到了这一点。

你可以同时考虑以下几点:

SML Warning: Type Vars Not Generalized when using Empty Lists or NONE option Polymorphic function as return value and value restriction in SML The value restriction

【讨论】:

反应很好。 CloudML 看起来不错。您在底部提供的第二个链接的公认答案表明存在值限制解决的安全考虑。我想知道CloudML 是否通过接受这样的代码使其自身在某种程度上易受攻击(有点像在某些脚本语言中直接在用户输入上使用eval)。我不明白怎么做,但我不完全理解这个限制是什么。【参考方案2】:

一个稍微长一点的解决方案(我想出这个解决方案是通过尝试思考 map 的相关性):

fun len xs = (foldr op+ 0 o map (fn x => 1)) xs;

这里o 是组合运算符。我想写

val len  = foldr op+ 0 o map (fn x => 1); 

为了模仿 Haskell 中流行的那种无点风格,但遇到了与您的原始定义完全相同的值限制。

【讨论】:

尼特:几乎所有的括号都是不必要的。 @AndreasRossberg Nits 应该被移除。感谢您指出。【参考方案3】:

给定一些standard definitions 用于foldlfoldr

fun foldr f e []      = e
  | foldr f e (x::xr) = f(x, foldr f e xr);

fun foldl f e []      = e
  | foldl f e (x::xr) = foldl f (f(x, e)) xr;

你可以手动将你的函数应用到一个简短的列表中,看看术语重写是如何展开的:

foldr (fn(_,y) => 1+y) 0 [5,6,7]
(fn(_,y) => 1+y) (5,foldr (fn(_,y) => 1+y) 0 [6,7])
(fn(_,y) => 1+y) (5,(fn(_,y) => 1+y) (6,foldr (fn(_,y) => 1+y) 0 [7]))
(fn(_,y) => 1+y) (5,(fn(_,y) => 1+y) (6,(fn(_,y) => 1+y) (7,foldr (fn(_,y) => 1+y) 0 [])))
(fn(_,y) => 1+y) (5,(fn(_,y) => 1+y) (6,(fn(_,y) => 1+y) (7,0)))
(fn(_,y) => 1+y) (5,(fn(_,y) => 1+y) (6,1))
(fn(_,y) => 1+y) (5,2)
3

使用foldr,您可以看到首先解析元素7(它从右向左折叠),并且表达式的长度(以及堆栈内存)与列表成比例增长。使用foldl,您可以看到首先解析5(它从左到右折叠)并且表达式的长度是恒定的。在这两种情况下,匿名函数的参数的第一部分都会被丢弃。

foldl (fn(_,y) => 1+y) 0 [5,6,7]
foldl (fn(_,y) => 1+y) ((fn(_,y) => 1+y)(5, 0)) [6,7]
foldl (fn(_,y) => 1+y) 1 [6,7]
foldl (fn(_,y) => 1+y) ((fn(_,y) => 1+y)(6, 1)) [7]
foldl (fn(_,y) => 1+y) 2 [7]
foldl (fn(_,y) => 1+y) ((fn(_,y) => 1+y)(7, 2)) []
foldl (fn(_,y) => 1+y) 3 []
3

诚然,lambdas 把整个事情搞得一团糟。给定定义

fun plus1(_,y) = 1+y

以下重写是等效的,但更易读。

foldr plus1 0 [5,6,7]
plus1 (5, foldr plus1 0 [6,7])
plus1 (5, plus1 (6, foldr plus1 0 [7]))
plus1 (5, plus1 (6, plus1 (7, foldr plus1 0 [])))
plus1 (5, plus1 (6, plus1 (7, 0)))
plus1 (5, plus1 (6, 1))
plus1 (5, 2)
3

foldl plus1 0 [5,6,7]
foldl plus1 (plus1 (5,0)) [6,7]
foldl plus1 1 [6,7]
foldl plus1 (plus1 (6,1)) [7]
foldl plus1 2 [7]
foldl plus1 (plus1 (7,2)) []
foldl plus1 3 []
3

【讨论】:

折叠的很好的解释*

以上是关于SML/NJ - 使用 foldr 的一种线长函数的主要内容,如果未能解决你的问题,请参考以下文章

SML 中的数据类型和类型未按预期工作

如何使用 foldr/foldl 定义 foldM(如果可能)?

有4个参数的foldr?

haskell中的foldr地图是什么类型的?

一种线上服务日志切分与压缩方法

matlab三维网格绘图函数meshmeshcmeshzmeshgridsurfmatlab图行绘制五