首先让我解释一下,为什么unit
发挥作用。
在OCaml if/then/else
不是一个语句,它是一个表达式,就像C语言中的三元运算符或类似于Python中的条件表达式一样。比意味着,这是一个表达它总是必须有一个价值。你不能只给出真正的分支表达式,并省略else分支,除非else分支的值是微不足道的并且总是被编译器知道。而后者仅适用于unit
类型。由于这种类型只有一个值,所以如果你的真正的分支返回一个单位类型的值,编译器已经知道什么会返回假分支。这就是为什么你可以忽略其他表达式的部分,即单位。省略else部分对于编译器来说是令人满意的证据,表达式的类型为unit
。这意味着,在表达式if (p x) then x
中,编译器决定x
的类型为unit
,因为没有其他部分。
现在的任务。 map
必须为列表的每个元素返回一个值。它不能跳过或重新排列,或者改变结构。为此,还有其他更高阶的功能,称为filter_map
,concat_map
,filter
等。
但是,让我们尝试做一些事情,而不用离开原来的措辞。回到你的例子,我们需要在其他部分做些事情。我们应该回归以指定没有价值?我们可以返回None
即option
类型,例如值,
if p x then Some x else None
请注意,我们还需要提升的部分,然后向option
类型。因此,我们将有一个类型为'a option list
的清单。然后我们需要过滤它,删除None
s。
另一种选择是返回,而不是None
空列表(又名nil
):
if p x then [x] else []
然后,我们将有一个'a list list
可以很容易地转化为'a list
与concat
操作。此外,我们可以看到,没有必要创建一个中间表,我们就可以在地方申请f
(即,对于这里毁林优化的机会):
if p x then [f x] else []
最后我们:
let r f p l = concat (map (fun x -> if p x then [f x] else []) l)
以后,你会发现,这两个option
和list
的单子,这招用map
和concat
实际上是对所有单子的核心业务,称为bind
并表示为>>=
。通过此操作员定义,我们可以写r
功能更优雅:
let r f p l = l >>= fun x -> if p x then [f x] else []
其中bind
运营商可以实现(低效率),作为
let (>>=) x f = concat (map f x)
但这一切功能天书,在实际世界编程,最好只使用fold_left
(如Jeffrey建议的),并将结果累积到辅助列表中,而不会忘记将其反转:
let r f p l = rev (fold_left (fun xs x -> if p x then f x :: xs else xs) [] l)
来源
2015-04-22 01:01:11
ivg