2015-04-21 99 views
0

所以我有一个函数r,它应该将一个函数应用于列表中的每个元素,只要它满足给定的谓词并返回该列表。即在Ocaml中处理单元

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分别为整数表达式,它预计单位表达。我哪里做错了?

回答

1

map函数将函数应用于列表的每个元素并返回结果列表。它总是返回一个与输入列表等长的列表。所以你的问题陈述并不完全合理。

在较低级别,表达式if (p x) then x仅在x具有类型单位时才合法。即,if b then e的含义与if b then e else()相同,并且if的两侧必须是相同的类型。

如果要返回与输入列表长度不同的列表,则需要使用折叠功能而不是map

3

首先让我解释一下,为什么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等。

但是,让我们尝试做一些事情,而不用离开原来的措辞。回到你的例子,我们需要在其他部分做些事情。我们应该回归以指定没有价值?我们可以返回Noneoption类型,例如值,

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 listconcat操作。此外,我们可以看到,没有必要创建一个中间表,我们就可以在地方申请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) 

以后,你会发现,这两个optionlist的单子,这招用mapconcat实际上是对所有单子的核心业务,称为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)