2014-04-30 86 views
1

我尝试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,或者是否有我不在这里的东西?

任何想法我可以在这里做什么来获得这些工作,或者这是可能的吗?我对Scala更先进的类型和类型lambda非常陌生,但我正在尝试了解它们如何工作来创建更多的类型安全代码。

我还注意到,Miles Sabin有一个名为Shapeless的图书馆(),我正在研究这个图书馆,但是找不到任何可以做同样的事情的东西,我试图在这里实现。

+1

我不知道这是为什么不编译,但即使它的类型擦除会使你所做的事情变得危险。你将引用'Option [String]'或'Option [Int]',并且由于类型擦除,你将无法将它们分开。这绝对不是“更安全的代码”。一个更好的解决方案是声明你自己的从一个公共子类降序的类集合,一个用于你正在联合的每个类型,一个用来替换'None'。这实际上具有更强的输入,因为你对这个用例有自己特定的类型,而不是重用'Option'。 – wingedsubmariner

+0

你的意思是有一个UpdateType类,然后有像UpdateTypeString,UpdateTypeNone等子类?难道这不仅仅是一种不那么通用的方式来完成我想要在类型联合中完成的工作吗?在这两种情况下,我都需要在结尾模式中匹配类型。 – Klaus

+0

是的,但是使用UpdateType类和孩子,模式匹配实际上可以工作。另外,如果UpdateType被标记为'sealed',编译器可以检查你没有忘记任何类型。 OO真的是在Scala中做到这一点的正确方式,类型级编程适合人们炫耀,但不属于生产代码。 – wingedsubmariner

回答

7

不幸的是,Sabin的联合类型不能单独用作返回类型。您的方法的上下文绑定[T : (UpdateType)#λ]只是一个额外的隐式参数(implicit evidence: (UpdateType)#λ[T])的语法糖。该参数是<:<的一个实例,证明T是联合类型组件之一的子类型。编译器必须在方法的调用位置填写参数,并且要做到这一点,它需要知道工会类型T中的哪些组件。当T是其中一个参数的类型时,这不是问题,因为编译器将具有该类型。当使用T作为返回类型时,编译器无法知道T会变成什么样子。更糟糕的是,在方法本身内部,T和隐式参数已经被设置,所以编译器只能接受T作为返回值,并且T只是联合类型的组件之一,而不是真正的联合类型可以代表其中任何一个。这就是为什么你得到编译器错误,坚持认为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的延伸或串就不会在这里直接受理...这里输入的值被限制为Int with String的“逆向”。

有解决这个寿的方式,你必须规避类型推断,并在类型参数中提供的基类,像这样:

sizeOf[String](someExtendOfString)