使用Either的别名在Scala中写入类似联合类型的Ceylon

时间:2013-11-14 13:05:01

标签: scala types

随着Ceylon 1.0的发布,一些人正在讨论联合类型的有用性。我想知道你如何简洁地编写以下代码:

String test(String | Integer x) {
  if (x is String) {
     return "found string";
  } else if (x is Integer) {
     return "found int";
  }
  return "why is this line needed?";
}

print(test("foo bar"));  // generates 'timeout'... well, whatever

...在Scala中?我的想法是这样的:

type | [+A, +B] = Either[A, B]

object is {
  def unapply[A](or: Or[A]): Option[A] = or.toOption

  object Or {
    implicit def left[A](either: Either[A, Any]): Or[A] = new Or[A] {
      def toOption = either.left.toOption
    }
    implicit def right[A](either: Either[Any, A]): Or[A] = new Or[A] {
      def toOption = either.right.toOption
    }
  }
  sealed trait Or[A] { def toOption: Option[A] }
}

def test(x: String | Int) = x match {
  case is[String](s) => "found string"   // doesn't compile
  case is[Int   ](i) => "found int"
}

但是模式提取器不能编译。有什么想法吗?

我知道that a similar question exists有一些有用的答案,但我特别想知道是否可以使用Either和提取器的类型别名。即使定义了Either以外的新类型,解决方案也应该允许穷举模式匹配。

4 个答案:

答案 0 :(得分:0)

这是第二次尝试,以防万一。它以隐式解决方式失败:

trait OrLike {
  type A
  type B

  def left : Option[A]
  def right: Option[B]
}

object | {
  implicit def left[A, B](value: A): | [A, B] = new | [A, B] {
    def left  = Some(value)
    def right = None
  }

  implicit def right[A, B](value: B): | [A, B] = new | [A, B] {
    def left  = None
    def right = Some(value)
  }
}
sealed trait | [A1, B1] extends OrLike {
  type A = A1
  type B = B1
}

object is {
  def unapply[A](or: OrLike)(implicit ev: Or[A]): Option[A] = ev.toOption

  object Or {
    implicit def left[A1](peer: OrLike { type A = A1 }): Or[A1] = new Or[A1] {
      def toOption = peer.left
    }
    implicit def right[B1](peer: OrLike { type B = B1 }): Or[B1] = new Or[B1] {
      def toOption = peer.right
    }
  }
  sealed trait Or[A] { def toOption: Option[A] }
}

def test(x: String | Int) = x match {
  case s is String => "found string"  // no evidence of `Or[A]`
  case i is Int    => "found int"
}

test("foo")

答案 1 :(得分:0)

我的尝试。没有通用提取器。我会试着想一想以后怎么做。

sealed trait | [+A, +B]

case class Left[+A](left: A) extends |[A, Nothing]

case class Right[+B](right: B) extends |[Nothing, B]

implicit def toLeft[A, B](a: A): |[A, B] = Left(a)
implicit def toRight[A, B](b: B): |[A, B] = Right(b)

object IsString {
    def unapply(x: |[_, _]): Option[String] = {
        x match {
            case Left(s: String) => Some(s)
            case Right(s: String) => Some(s)
            case _ => None
        }
    }
}

object IsInt {
    def unapply(x: |[_, _]): Option[Int] = {
        x match {
            case Left(i: Int) => Some(i)
            case Right(i: Int) => Some(i)
            case _ => None
        }
    }
}

def test(x: String | Int) = x match {
  case IsString(s) => s"found string: $s"
  case IsInt(i)    => s"found int: $i"
}

println(test(10))
println(test("str"))

答案 2 :(得分:0)

我也有一个或多或少的工作实现Miles Sabin的C-H想法(上面链接)。不确定这是否直接解决了你的问题,但也许这是有用的思考。

源代码为in this project on GitHub

以下是一个快速使用示例:

type ISB = union [Int] #or [String] #or [Boolean]

def unionFunction[T: prove [ISB] #containsType](t: T) {}

unionFunction(55)
unionFunction("hello")
unionFunction(false)
// unionFunction(math.Pi) // fails to compile (correct behavior)

还提供了一个用于装箱这些类型的类,因为这通常很方便:

val wrapped = new Union[union [Int] #or [String]]

wrapped.contains[Int] // true
wrapped.contains[String] // true
wrapped.contains[Double] // false

wrapped assign 55
wrapped.value[Int] // Some(55)
wrapped.value[String] // None

wrapped assign "hi, union!"
wrapped.value[String] // Some("hi, union!")
wrapped.value[Int] // None

def unionFunction[T: wrapped.containsType] {}
unionFunction[Int] // compiles :)
unionFunction[String] // compiles :)

另一个有趣的事情是API允许联合类型一次构建一个成员类型,如下所示:

val unary = new Union[union [Int] #apply]
val binary = new Union[unary.underlying #or [String]]
val ternary = new Union[binary.underlying #or [Float]]
ternary.typeMembers // Seq(typeOf[Int], typeOf[String], typeOf[Float])

答案 3 :(得分:0)

对于记录,Dotty具有联合类型,因此示例为

def test(x: String | Int): String = x match {
  case _: String => "found string"
  case _: Int    => "found int"
}

println(test("foo bar"))

Scastie link