在没有Monoid实例的类型上折叠

时间:2013-10-28 00:50:46

标签: scala

我正在进行这项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))
}

这种理解和代码是否正确?

1 个答案:

答案 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 => BAB可能只有一个可能有用的值,但有很多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))
  }

这相当且效率更高,但在我的眼中却不那么明确,所以我建议将其视为任何优化 - 如果你需要它,使用它,但要三思我的收益是否真的值得失去清晰度。