Rust中是否有支持类似Haskell的“map”函数的集合?

3
在函数式编程语言中,集合上最原始/基本的操作是同态映射 `map`;它(大致上)是 `Collection[A] -> (A->B) -> Collection[B]`。
Rust 集合似乎不支持这个操作。我猜这是因为它们是可变集合;如果你已经在使用可变集合,那么原地更新更加高效。
是否有一个单独的“不可变集合”库(类似于 Scala)我错过了?
那么,“原地 map”操作呢?它使用 `A->B` 将 `Collection[A]` 变异为 `Collection[B]`(与 ML 和 Haskell 不同,由于关联类型,这实际上是安全的!)甚至特殊情况下,其中 `A=B` 并且原地映射采用 `A->A`?
很难用搜索引擎回答这个问题,因为所有的结果都涉及到 “map” 这个名词(如 `HashMap`)。

我们拥有强大的迭代器和collect(),满足您所有的FP需求。什么是原地映射? - Tatsuyuki Ishi
1
在 Haskell 中,你可以使用 fmap 并保持结构不变。例如,如果你有一个哈希表,如果你使用 fmap 改变值,那么键中的任何一个都不需要重新计算哈希值。 - Dietrich Epp
@TatsuyukiIshi:我没有意识到在map函数中应该查找迭代器。谢谢! - user4718
2个回答

4
Rust有一个map()函数,但它不是每个独立的容器的一部分,而是属于特质Iterator(Rust特质与Haskell的类型类非常相似):Iterator:map()。该特性包含许多有用的方法,其中很多应该听起来很熟悉FP程序员。
让我们看看map()的实际用法:
let result: Vec<_> = vec![2, 3, 5, 7]
    .into_iter()      // we have to get an iterator over the vector 
    .map(|i| i * i)   // next we map each element
    .collect();       // finally we collect all elements into a new vector
< p > map() 的类型就像你所期望的那样:< /p>
:: Iterator a -> (a -> b) -> Iterator b

或者用Rust语法表示:

trait Iterator {
    type Item;
    fn map<B, F>(self, f: F) -> Map<Self, F> 
        where F: FnMut(Self::Item) -> B;
}

这个签名看起来更加复杂,但实际上它是有意义的,并且在将来可能会更好。 签名是 (self, f: F) -> Map<Self,F>。还有:

  • self 是一个 Self::Item 的迭代器[对比:Iterator a]
  • F 是一个 FnMut(Self::Item) -> B[ 对比:(a -> b) ]
  • Map<Self,F> 是一个 F 返回值 (B) 的迭代器[ 对比:Iterator b ]

如果您想进行就地 a -> a 映射(即:不更改类型),则可以获取每个元素的可变引用并更改它。示例:

let mut v = vec![2, 3, 5, 7];
for e in &mut v {
    e *= 2;
}

let mut m = HashMap::new();
m.insert("anna", 5);
m.insert("peter", 3);
for v in m.values_mut() {
    v *= 2;
}

目前无法在不使用unsafe代码的情况下执行改变类型的in-place映射。这在一定程度上因为Rust的类型系统无法在编译时比较两种类型的大小(但它将会改变)。


哇@Lukas,非常感谢您详细的回答!请不要认为我对之前接受Tatsuyuki的答案不感激。最终,我所缺少的就是在Rustland中,“map”是针对迭代器而非它们来自的集合进行操作的事实。谢谢!! - user4718
还有感谢关于Trait与Haskell类型类的评论。我已经想到这一点了,尽管我希望Rust手册能简单地提一下这个(以及Java中的接口等最流行的十几种语言中的相应功能)。目前写成这样,Rust文档让trait看起来像是某种新奇而强大的东西,然而就我所知,它们并不是如此。相比之下,比如借用检查器在除Clean和Mercury之外任何生产语言/编译器中都没有类似的东西。 - user4718
Rust的类型系统无法在编译时比较两种类型的大小。嗯,我认为能够确定每种类型的大小(其中Boxed类型的大小当然是指针的大小)是Rust类型系统中许多决策的主要驱动力。是否有适用于具有PL背景的人的Rust类型系统描述?不一定需要序列和推断,我意识到Rust仍在快速发展,而ML具有的形式化方法会以缓慢的演变为代价。 - user4718
@user4718 "... can't compare the sizes ..." -> 我的表述有些含糊。我的意思是,您(作为普通用户)无法编写具有两个类型参数的函数,并在编译时断言类型的大小相等。当然,您可以在运行时检查它并 panic!(),但那不太好。所有这些都与在类型级别上玩弄整数的能力有关,目前还不可能。当然,编译器知道大小,用户只是无法在编译时“使用”它们。希望现在更清楚了。 - Lukas Kalbertodt

3

要将容器映射到容器,需要迭代并使用collect()函数:

vec.iter().map(|x| x * 2).collect::<Vec<_>>();

要就地修改项目,请使用for循环和iter_mut()。在这种情况下不建议使用迭代器链,因为修改值会引入副作用。

for x in vec.iter_mut() { 
    x *= 2; 
}

啊,非常感谢。我今天学到 Rust 中的 map 函数是迭代器的一部分,而不是集合。 - user4718
2
除了 for x in vec.iter_mut(),你也可以写成 for x in &mut vec - kennytm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接