Как вернуть кортеж внутри EitherT

Я использую EitherT в Scalaz 7 для создания сложных понятий, которые смешивают State и \/. Все идет нормально; Я получаю то, что в основном:

State[MyStateType, MyLeftType \/ MyRightType]

и это позволяет мне создавать для-понимания, которые имеют хорошие переменные в левой части <-.

Но я не могу понять, как вернуть кортежи из государственного действия. Одиночные результаты просто хороши - в приведенном ниже коде "понимание в val" - именно то, чего я хочу достичь.

Но вещи рушатся, когда я хочу вернуть кортеж; "val otherComprehension" не позволит мне сделать

(a, b) <- comprehension

Похоже, он ожидает, что левая сторона \ / будет моноидом, и я не понимаю, почему. Что мне не хватает?

(Scalaz 7 2.0.0-SNAPSHOT, Scala 2.10.2)

object StateProblem {
  case class MyStateType
  case class MyRightType
  case class MyLeftType

  type StateWithFixedStateType[+A] = State[MyStateType, A]
  type EitherTWithFailureType[F[+_], A] = EitherT[F, MyLeftType, A]
  type CombinedStateAndFailure[A] = EitherTWithFailureType[StateWithFixedStateType, A]

  def doSomething: CombinedStateAndFailure[MyRightType] = {
    val x = State[MyStateType, MyLeftType \/ MyRightType] {
      case s => (s, MyRightType().right)
    }
    EitherT[StateWithFixedStateType, MyLeftType, MyRightType](x)
  }

  val comprehension = for {
    a <- doSomething
    b <- doSomething
  } yield (a, b)

  val otherComprehension = for {
    // this gets a compile error:
    // could not find implicit value for parameter M: scalaz.Monoid[com.seattleglassware.StateProblem.MyLeftType]
    (x, y) <- comprehension

    z <- doSomething
  } yield (x, y, z)
}

Изменить: я добавил доказательства того, что MyLeftType является монадой, хотя это не так. В моем реальном коде MyLeftType является классом case (называемым EarlyReturn), поэтому я могу предоставить ноль, но добавление работает, только если один из аргументов равен нулю:

  implicit val partialMonoidForEarlyReturn = new Monoid[EarlyReturn] {
    case object NoOp extends EarlyReturn
    def zero = NoOp
    def append(a: EarlyReturn, b: => EarlyReturn) =
      (a, b) match {
        case (NoOp, b) => b
        case (a, NoOp) => a
        case _         => throw new RuntimeException("""this isnt really a Monoid, I just want to use it on the left side of a \/""")
      }
  }

Я не уверен, что это хорошая идея, но она решает проблему.

2 ответа

Решение

Как я отмечаю в комментарии выше, проблема в том, что версия вашего второго for-conprehension включает в себя операцию фильтрации в 2.10.2 (и 2.10.1, но не 2.10.0), и это не возможно для фильтрации EitherT (или просто старый \/) без моноида для типа на левой стороне.

Довольно легко понять, почему моноид необходим в следующем примере:

val x: String \/ Int = 1.right
val y: String \/ Int = x.filter(_ < 0)

Что такое y? Понятно, что это должен быть какой-то "пустой" String \/ Int, и с тех пор \/ смещен вправо, мы знаем, что это не может быть ценностью с этой стороны. Таким образом, нам нужен ноль для левой стороны, а экземпляр моноида для String обеспечивает это - это просто пустая строка:

assert(y == "".left)

Согласно этому ответу на мой связанный с этим вопрос о шаблонах кортежей в for-понимания, поведение, которое вы видите в 2.10.2, является правильным и предназначенным - очевидно, совершенно ненужный вызов withFilter здесь, чтобы остаться.

Вы можете использовать обходной путь в ответе Петра Пудлака, но также стоит отметить, что следующая версия без сахара также довольно ясна и лаконична:

val notAnotherComprehension = comprehension.flatMap {
  case (x, y) => doSomething.map((x, y, _))
}

Это более или менее то, что я наивно ожидал for-понимание, чтобы desugar, в любом случае (и я не единственный).

Не зная причину, я нашел возможный обходной путь:

for {
  //(x, y) <- comprehension
  p <- comprehension

  z <- doSomething
} yield (p._1, p._2, z)

или, может быть, немного лучше

for {
  //(x, y) <- comprehension
  p <- comprehension
  (x, y) = p

  z <- doSomething
} yield (x, y, z)

Это не очень хорошо, но делает работу.

(Я действительно ценю, что вы сделали автономный, рабочий пример проблемы.)

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