定义依赖于自身的Semigroup实例

时间:2016-09-21 16:32:00

标签: scala functional-programming typeclass scala-cats semigroup

...或者是必须编写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编译器无法为Semigroup的{​​{1}}派生实例。这似乎得到了证实,因为以下实例是编译的:

Map[String, ResourceTree]

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

有更好的方法吗?

1 个答案:

答案 0 :(得分:4)

以下应该可以正常工作:

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

关键是错误消息的这一部分:“它缺少显式结果类型”。 Scala中的递归方法必须具有显式的返回类型,并且类似地依赖于它们的类型实例(在这种情况下直接或间接通过诸如Map实例和|+|语法之类的东西)也需要它们。 / p>

一般来说,在所有隐式定义上放置显式返回类型是个好主意 - 不这样做会导致意外行为,如果您考虑并阅读规范,其中一些是有意义的(就像在这种情况下),其中一些似乎只是编译器中的bugginess。