3

我正在做这个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.

据我了解这个练习,这意味着,如果我们有一个类型为 Monoid B,但我们的输入 List 是类型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))
}

这种理解和代码看起来对吗?

4

1 回答 1

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 如何使用隐式参数解析实现类型类的更多详细信息,请参阅原始的“类型类作为对象和隐式”论文,或者我在上面也链接的 Rex Kerr 的这个答案。

接下来我将切换其他两个参数列表的顺序:

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在这种情况下,任何Aand可能只有一个可能有用的值B,但是 有很多值List[A]

例如,切换顺序允许我们编写以下代码(假设 有一个幺半群实例Bar):

val fooSum: List[Foo] => Bar = foldMap(fooToBar)

最后,作为性能优化(上面的stewf提到),您可以通过将应用程序移动到折叠中来避免创建中间列表:

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))
  }

这是等效且更有效的,但在我看来不太清晰,因此我建议将其视为任何优化 - 如果您需要它,请使用它,但要三思而后行是否真的值得失去清晰度。

于 2013-10-28T08:05:13.643 回答