Scala联合类型与闭包

时间:2014-04-30 01:15:23

标签: scala shapeless

我正在尝试在Miles Sabin博客文章中定义的Scala联盟类型:

http://www.chuusai.com/2011/06/09/scala-union-types-curry-howard/

并在

中讨论过

How to define "type disjunction" (union types)?

对于那里定义的简单情况,它们可以正常工作,但我想要做的就是使用它们在Play Framework中创建一个只接受某些值的通用JSON解析器(String,Boolean,Int,Undefined)然后成功传递它们。

这是我的代码:

type UpdateType = Option[String] |∨| Option[Int] |∨| Option[Boolean] |∨| Option[List[Int]]

def withValue[T : (UpdateType)#λ](request: Request[JsValue])(block: (String, T) => Future[SimpleResult]) = {
  val field = request.body \ ("field")
  val value = request.body \ ("value")
  (field, value) match {
    case (x: JsString, y: JsString) => block(x.value.toString, Some(y.value.toString))
    case (x: JsString, y: JsNumber) => block(x.value.toString, Some(y.value.intValue))
    case (x: JsString, y: JsBoolean) => block(x.value.toString, Some(y.value.booleanValue))
    case (x: JsString, y: JsUndefined) => block(x.value.toString, None)
    case _ => Future.successful(BadRequest(s"Incorrect field, value pair for ${request.body}."))
  }
}

然后我想以这种方式使用它:

def update(code: String) = Action.async(parse.json) { 
  request =>
    withValue(request) { (field, value) =>
      // Code that does something with the value
      Future(Ok)  
    }
}

但我的withValue函数给出了编译错误:

[error]  found   : Some[String]
[error]  required: T
[error]       case (x: JsString, y: JsString) => block(x.value.toString, Some(y.value.toString))

不应该是UpdateType,它应该接受某些[String]或者有什么东西我没有到达这里吗?

知道我能在这里做些什么才能让这些工作或者有可能吗?我对Scala中更高级的类型和类型lambda非常陌生,但我正在尝试学习如何创建更安全的代码。

我还注意到Miles Sabin有一个名为Shapeless(https://github.com/milessabin/shapeless)的图书馆,我正在调查,但是找不到任何与我试图做同样事情的图书馆。在这里实现。

2 个答案:

答案 0 :(得分:7)

不幸的是,Sabin的联合类型不能仅用作返回类型。您的方法的上下文绑定[T : (UpdateType)#λ]只是额外隐式参数(implicit evidence: (UpdateType)#λ[T])的语法糖。此参数是<:<的实例,证明T是联合类型的某个组件的子类型。编译器必须在方法的调用站点填写参数,为此,它需要知道联合类型T的哪些组件将是什么。当T是其中一个参数的类型时,这不是问题,因为编译器将拥有该类型。当T用作返回类型时,编译器无法知道T将会是什么。更糟糕的是,在方法本身内部,T和隐含参数已经一成不变,因此编译器只能接受T作为返回值,T只有一个union类型的组件,而不是可以表示其中任何一个的真正联合类型。这就是为什么你得到编译器错误,坚持Some[String]不能用作T

没有任何简单的方法可以解决这个问题,因为Scala并不真正拥有联合类型。正如我在问题的评论中所提到的,有一个简单的OO解决方案,尽管有更多的样板。

答案 1 :(得分:0)

让我向您介绍我的解决方案:

//Add this to your util library
trait Contra[-A]
type Union[A,B] = Contra[A] <:< Contra[B]

//And see a usage example below
@implicitNotFound("Only Int or String can be sized")
type Sizeable[T] = Union[T, Int with String]

def sizeOf[T: Sizeable](sizeable: T): Int = {
  sizeable match {
    case i: Int => i
    case s: String => s.length
  }
}

此解决方案的问题是此处不会直接接受Int或String的扩展...此处输入的值被限制为与Int with String“逆变”。

有一种解决方法,你必须绕过类型推断,并在type参数中提供基类,如下所示:

sizeOf[String](someExtendOfString)