Определение экземпляра полугруппы, который зависит от самого себя

... или неудачи программиста на Haskell, который должен кодировать Scala, часть 5.

У меня есть следующая структура в Scala:

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

И, используя кошек, я хотел бы определить 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 из Map[String, ResourceTree], Кажется, это подтверждается, поскольку следующий экземпляр компилируется:

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 на этом языке.

Есть ли способ лучше?

1 ответ

Решение

Следующее должно работать просто отлично:

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 экземпляр и |+| синтаксис в этом случае) тоже нужны.

В целом, это хорошая идея - указывать явные возвращаемые типы во всех неявных определениях - если вы этого не сделаете, это может привести к неожиданному поведению, некоторые из которых имеют смысл, если вы подумаете об этом и прочитаете спецификацию (как в этом случае), а некоторые из которых Просто кажется, что ошибка в компиляторе.

Другие вопросы по тегам