Обойти инвариантный тип результата в State - PullRequest
0 голосов
/ 23 декабря 2018

Я хотел бы определить State, который создает конкретный подтип черты, согласно decodeFoo:

sealed trait Foo
case class Bar(s: String) extends Foo
case class Baz(i: Int) extends Foo

val int: State[Seq[Byte], Int] = State[Seq[Byte], Int] {
  case bs if bs.length >= 4 =>
    bs.drop(4) -> ByteBuffer.wrap(bs.take(4).toArray).getInt
  case _ => sys.error(s"Insufficient data remains to parse int")
}

def bytes(len: Int): State[Seq[Byte], Seq[Byte]] = State[Seq[Byte], Seq[Byte]] {
  case bs if bs.length >= len => bs.drop(len) -> bs.take(len)
  case _ => sys.error(s"Insufficient data remains to parse $len bytes")
}

val bytes: State[Seq[Byte], Seq[Byte]] = for {
  len <- int
  bs <- bytes(len)
} yield bs

val string: State[Seq[Byte], String] = bytes.map(_.toArray).map(new String(_, Charset.forName("UTF-8")))

val decodeBar: State[Seq[Byte], Bar] = string.map(Bar)
val decodeBaz: State[Seq[Byte], Baz] = int.map(Baz)

val decodeFoo: State[Seq[Byte], Foo] = int.flatMap {
  case 0 => decodeBar
  case 1 => decodeBaz
}

Это не скомпилируется, так как State определено в кошках как type State[S, A] и компилятор отвечает:

Error:(36, 15) type mismatch;
 found   : cats.data.State[Seq[Byte],FooBarBaz.this.Bar]
    (which expands to)  cats.data.IndexedStateT[cats.Eval,Seq[Byte],Seq[Byte],FooBarBaz.this.Bar]
 required: cats.data.IndexedStateT[cats.Eval,Seq[Byte],Seq[Byte],FooBarBaz.this.Foo]
Note: FooBarBaz.this.Bar <: FooBarBaz.this.Foo, but class IndexedStateT is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
    case 0 => decodeBar

Я могу обойти эту проблему, расширив определения decodeBar & decodeBaz до типа State[Seq[Byte], Foo].Это лучший путь вперед?Или я могу использовать другой подход, который позволяет избежать расширения этих типов?

1 Ответ

0 голосов
/ 23 декабря 2018

Functor.widen

Functor.widen должно сработать.Полный скомпилируемый пример (с добрым проектором):

import cats.data.State
import cats.Functor

object FunctorWidenExample {
  locally {
    sealed trait A
    case class B() extends A

    val s: State[Unit, B] = State.pure(new B())
    val t: State[Unit, A] = Functor[State[Unit, ?]].widen[B, A](s)
  }
}

в вашем случае это будет что-то вроде:

val decodeFoo: State[Seq[Byte], Foo] = int.flatMap {
  case 0 => Functor[State[Seq[Byte], ?]].widen[Bar, Foo](decodeBar)
  case 1 => Functor[State[Seq[Byte], ?]].widen[Bar, Foo](decodeBaz)
}

Другие возможные обходные пути

(на самом деле не обязательно, просто чтобы продемонстрировать синтаксис, который может быть менее известен):

  • Явные описания типов:

    val decodeFoo: State[Seq[Byte], Foo] = int.flatMap {
      case 0 => decodeBar.map(x => (x: Foo))
      case 1 => decodeBaz.map(x => (x: Foo))
    }
    
  • Использование <:< в качестве метода (у этих вещей действительно есть значимое apply):

    val decodeFoo: State[Seq[Byte], Foo] = int.flatMap {
      case 0 => decodeBar.map(implicitly: Bar <:< Foo)
      case 1 => decodeBaz.map(implicitly: Baz <:< Foo)
    }
    
...