首页 文章

在Ocaml处理单位

提问于
浏览
0

所以我有一个函数 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 回答

  • 4

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

    但是,让我们尝试做一些事情而不留下原来的措辞 . 回到你的例子,我们需要在else部分做一些事情 . 我们应该回归什么,指出没有 Value ?我们可以返回 None ,它是 option 类型的值,例如,

    if p x then Some x else None
    

    请注意,我们还需要将then部分提升为 option 类型 . 因此,我们将有一个类型 'a option list 的列表 . 然后我们需要过滤它,删除 None .

    其他选择是返回一个空列表(又名 nil ),而不是 None

    if p x then [x] else []
    

    然后我们将 'a list list 可以通过 concat 操作轻松转换为 'a list . 此外,我们可以注意到,没有必要创建一个中间列表,我们可以恰当地应用 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 都是monad,而 mapconcat 的这个技巧实际上是所有monad的核心操作,称为 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)
    

    但这一切都是功能性的mumbo-jumbo,在现实世界的编程中,最好只使用 fold_left (如Jeffrey建议的那样),并将结果累积到辅助列表中,而不会忘记反转它:

    let r f p l = rev (fold_left (fun xs x -> if p x then f x :: xs else xs) [] l)
    
  • 1

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

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

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

相关问题