定义一个依赖自身的 Semigroup 实例

Defining a Semigroup instance that depends on itself

... 或必须编写 Scala 代码的 Haskell 程序员的不幸事故,第 5 部分。

我在 Scala 中有以下结构:

case class ResourceTree(
  resources: Map[String, ResourceTree]
) 

并且,使用 Cats,我想定义它的一个 Semigroup 实例。

object ResourceTreeInstances {
  implicit val semigroupInstance = new Semigroup[ResourceTree] {
    override def combine(x: ResourceTree, y: ResourceTree): ResourceTree = {
      ResourceTree(
        x.resources |+| y.resources
      )
    }
  }

这将导致以下错误:

value |+| is not a member of Map[String, ResourceTree]
[error]  Note: implicit value semigroupInstance is not applicable here because it comes after the application point and it lacks an explicit result type
[error]         x.resources |+| y.resource

所以,我的猜测是,由于我正在为 Semigroup 定义实例,因此 Scala 编译器无法为 Map[String, ResourceTree]Semigroup 派生实例。这似乎得到了证实,因为以下实例已编译:

implicit val semigroupInstance = new Semigroup[ResourceTree] {
  override def combine(x: ResourceTree, y: ResourceTree): ResourceTree = {
    dummyCombine(x, y)
  }
}

// FIXME: see if there's a better way to avoid the "no instance of Semigroup" problem
def dummyCombine(x: ResourceTree, y: ResourceTree): ResourceTree = {
  ResourceTree(
    x.resources |+| y.resources
  )
}

我真的希望我错了,因为如果这是在 Scala 中为半群定义实例的正确方法,我将开始考虑放弃使用这种语言进行 FP 的想法。

有没有更好的方法?

以下应该可以正常工作:

import cats.Semigroup
import cats.instances.map._
import cats.syntax.semigroup._

case class ResourceTree(resources: Map[String, ResourceTree]) 

implicit val resourceTreeSemigroup: Semigroup[ResourceTree] =
  new Semigroup[ResourceTree] {
    def combine(x: ResourceTree, y: ResourceTree): ResourceTree =
      ResourceTree(
        x.resources |+| y.resources
      )
  }

关键是错误信息的这一部分:"and it lacks an explicit result type"。 Scala 中的递归方法必须具有明确的 return 类型,并且类似地类型 class 依赖于自身的实例(直接或间接地通过诸如 Map 实例和 |+| 语法之类的东西这种情况)也需要它们。

一般来说,将显式 return 类型放在 所有 隐式定义上是个好主意——不这样做会导致意外行为,如果您考虑并阅读规范(如本例),其中一些似乎是编译器中的错误。