我正在进行这项Functional Programming in Scala练习:
// But what if our list has an element type that doesn't have a Monoid instance?
// Well, we can always map over the list to turn it into a type that does.
我理解这个练习,这意味着,如果我们有一个B
类型的Monoid,但我们的输入列表是A
类型,那么我们需要转换List[A]
到List[B]
,然后拨打foldLeft
。
def foldMap[A, B](as: List[A], m: Monoid[B])(f: A => B): B = {
val bs = as.map(f)
bs.foldLeft(m.zero)((s, i) => m.op(s, i))
}
这种理解和代码是否正确?
答案 0 :(得分:2)
首先,我稍微简化了主体的语法:
def foldMap[A, B](as: List[A], m: Monoid[B])(f: A => B): B =
as.map(f).foldLeft(m.zero)(m.ops)
然后我将monoid实例移动到它自己的隐式参数列表中:
def foldMap[A, B](as: List[A])(f: A => B)(implicit m: Monoid[B]): B =
as.map(f).foldLeft(m.zero)(m.ops)
有关Scala如何使用隐式参数解析实现类型类的更多详细信息,请参阅原始"Type Classes as Objects and Implicits"文章,或者我已在上面链接的Rex Kerr使用this answer。
接下来我将切换其他两个参数列表的顺序:
def foldMap[A, B](f: A => B)(as: List[A])(implicit m: Monoid[B]): B =
as.map(f).foldLeft(m.zero)(m.ops)
通常,您希望放置包含首先更改频率较低的参数的参数列表,以使部分应用程序更有用。在这种情况下,对于任何A => B
和A
,B
可能只有一个可能有用的值,但有很多List[A]
的值。
例如,切换顺序允许我们编写以下内容(假设为Bar
的monoid实例):
val fooSum: List[Foo] => Bar = foldMap(fooToBar)
最后,作为性能优化(上面的stew提到),您可以通过将f
的应用程序移动到折叠中来避免创建中间列表:
def foldMap[A, B](f: A => B)(as: List[A])(implicit m: Monoid[B]): B =
as.foldLeft(m.zero) {
case (acc, a) => m.op(acc, f(a))
}
这相当且效率更高,但在我的眼中却不那么明确,所以我建议将其视为任何优化 - 如果你需要它,使用它,但要三思我的收益是否真的值得失去清晰度。