Scala联合类型与闭包

问题描述 投票:1回答:2

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

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

并在中讨论过

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

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

这是我的代码:

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)的图书馆,我正在调查,但找不到任何可以做我想在这里做的事情。

scala shapeless
2个回答
7
投票

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

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


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)
© www.soinside.com 2019 - 2024. All rights reserved.