所以我有一个函数 r
,它应该将一个函数应用于列表中的每个元素,只要它符合给定的谓词,并返回该列表 . 即
let p x = x > 2;;
let f x = x+1;;
r p f [1;2] => []
我正在使用 map
函数将函数应用于列表中的每个元素,然后返回该列表 . 因此,我对 r
的实现如下
let r f p l = map f (map (fun x -> if (p x) then x) l );;
但如果我试图像上面的例子中那样调用 r
,我会得到一个类型错误,因为f和p是整数的表达式,它预期单位的表达式 . 我哪里做错了?
2 回答
首先让我解释一下,为什么
unit
发挥作用 .在OCaml中,
if/then/else
不是一个语句,它是一个表达式,就像C语言中的三元运算符一样,或者像Python中的条件表达式一样 . 而不是意味着,作为一个表达它总是必须有一个 Value . 你不能只为真正的分支给出一个表达式,并省略else分支,除非else分支的值是微不足道的,并且总是为编译器所知 . 而后者只适用于unit
类型 . 由于此类型只驻留一个值,因此如果true分支返回类型为unit的值,则编译器已知道将返回false分支的内容 . 这就是为什么你可以省略表达式的其他部分,评估为单位 . 省略else部分是编译器满意的证据,即整个表达式的类型为unit
. 这意味着,在表达式if (p x) then x
中,编译器决定x
具有类型unit
,因为没有其他部分 .现在来完成任务 .
map
必须为列表的每个元素返回一个值 . 它不能跳过或重新排列,也不能改变结构 . 为此,还有其他更高阶的函数,称为filter_map
,concat_map
,filter
等 .但是,让我们尝试做一些事情而不留下原来的措辞 . 回到你的例子,我们需要在else部分做一些事情 . 我们应该回归什么,指出没有 Value ?我们可以返回
None
,它是option
类型的值,例如,请注意,我们还需要将then部分提升为
option
类型 . 因此,我们将有一个类型'a option list
的列表 . 然后我们需要过滤它,删除None
.其他选择是返回一个空列表(又名
nil
),而不是None
:然后我们将
'a list list
可以通过concat
操作轻松转换为'a list
. 此外,我们可以注意到,没有必要创建一个中间列表,我们可以恰当地应用f
(即,这里有一个森林砍伐优化的机会):最后我们有:
稍后,您会发现,
option
和list
都是monad,而map
和concat
的这个技巧实际上是所有monad的核心操作,称为bind
并表示为>>=
. 通过定义此运算符,我们可以更优雅地编写r
函数:其中
bind
运算符可以实现(效率低下),如但这一切都是功能性的mumbo-jumbo,在现实世界的编程中,最好只使用
fold_left
(如Jeffrey建议的那样),并将结果累积到辅助列表中,而不会忘记反转它:map
函数将函数应用于列表的每个元素并返回结果列表 . 它总是返回与输入列表相等长度的列表 . 所以你的问题陈述并没有完全合理 .在较低级别,如果x具有类型单位,则表达式
if (p x) then x
仅是合法的 . 即,if b then e
的含义与if b then e else ()
相同,并且if
的两侧必须是相同的类型 .如果要返回与输入列表不同长度的列表,则需要使用折叠函数而不是
map
.