Ограничить возвращаемый тип функции, которая принимает либо

Я пытаюсь сделать такую ​​функцию:

 def foo(x: Either[String, Int]) = x match {
   case Left(s) => Left("foo:" + s)
   case Right(n) => Right(n+1)
 }

Это работает, но я ищу способ убедить вызывающего, что результат всегда будет того же типа, что и ввод — если ввод «Левый», вы получите «Левый» обратно, если он был «Правый», вы получите «Правый».

Может ли кто-нибудь придумать изящный трюк, который я мог бы использовать для этого?

Я знаю, что могу это сделать:

 def foo[T <: Either[String, Int]](x: T): T = (x match {
   case Left(s) => Left("foo:" + s)
   case Right(n) => Right(n+1)
 }).asInstanceOf[T]

... но приведение в конце уродливое :( Это объявление будет абстрактным членом базового трейта, который нужно будет переопределить нескольким реализациям "плагинов", и я не хочу, чтобы все они должны сделать эту вещь типа литья.

Я также мог бы сделать две отдельные функции fooString и fooInt ... но этого я хотел бы избежать по некоторым соображениям, характерным для конкретного API, над которым я работаю.

Любые другие идеи?


person Dima    schedule 05.04.2017    source источник


Ответы (2)


Если вы не ограничены использованием Либо, вы можете использовать классы типов -

sealed trait Transformer[A] {
 def transform(n: A): A
}
object Transformer {
 implicit object IntTransformer extends Transformer[Int] { def transform(n: Int) = n + 1 }
 implicit object StringTransformer extends Transformer[String] { def transform(s: String) = "foo:" + s }
}

def foo[A: Transformer](x: A)(implicit transformer: Transformer[A]) = transformer.transform(x)
person Meghana Viswanath    schedule 05.04.2017
comment
Если вы хотите иметь дело конкретно с Either, вы также можете создать неявный Transformer[Left/Right/Either[String, Int]] (Either будет соответствовать шаблону по типу). - person Alexey Romanov; 05.04.2017

Эта подпись на самом деле не говорит того, что вы хотите сказать:

val x = Left("a")
val y = foo[x.type](x)

Тип y - x.type, поэтому это должен быть тот же экземпляр, что и не является. Отсюда следует, что вам нужно изменить подпись, если вы хотите избежать приведения. Один подход (не проверенный):

trait LowPriorityFooImplicits { _: FooImplicits =>
  implicit def eitherFoo(x: Either[String, Int]): Foo[Either[String, Int]] = new Foo(x) {
    def foo() = x match {
      case y: Left[String, Int] => y.foo()
      case z: Right[String, Int] => z.foo()
  }
}

trait FooImplicits extends LowPriorityFooImplicits {
  sealed trait Foo[A <: Either[String, Int]](x: A) {
    def foo(): A
  }

  implicit def leftFoo(x: Left[String, Int]): Foo[Left[String, Int]] = new Foo(x) {
    def foo() = Left(fooString(x.value))
  }
  implicit def rightFoo ...

  // x will be implicitly converted from a subtype of Either[String, Int]
  def foo[A](x: Foo[A]): T = x.foo()

  protected def fooString(s: String) = "foo:" + s
  protected def fooInt(n: Int) = n + 1
}

(У него все еще есть fooString и fooInt, но не в общедоступном API.)

person Alexey Romanov    schedule 05.04.2017