【问题标题】:Scala union types with closures带有闭包的 Scala 联合类型
【发布时间】:2014-04-30 01:15:22
【问题描述】:

我正在试用 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 解析器,它只接受某些值(字符串、布尔值、整数、未定义),然后顺利通过。

这是我的代码:

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 应该接受 Some[String] 还是有什么我没有得到这里?

知道我可以在这里做些什么来使这些工作正常进行吗?或者有可能吗?我对 Scala 中更高级的类型和类型 lambdas 很陌生,但我正在尝试了解它们如何工作以创建更多类型安全的代码。

我还注意到 Miles Sabin 有一个名为 Shapeless (https://github.com/milessabin/shapeless) 的库,我正在研究它,但在那里找不到任何可以与我在这里尝试实现的功能相同的东西。

【问题讨论】:

  • 我不确定为什么不编译,但即使它确实类型擦除也会使你正在做的事情变得危险。您将引用Option[String]Option[Int],并且由于类型擦除,您将无法区分它们。这绝对不是“更安全的代码”。更好的解决方案是声明您自己的从一个公共子类继承的类集合,一个用于您要联合的每种类型,一个用于替换None。这实际上具有更强的类型,因为您有自己的特定类型用于此用例,而不是重用 Option
  • 您的意思是拥有一个 UpdateType 类,然后拥有像 UpdateTypeString、UpdateTypeNone 等子类吗?这难道不是一种不太通用的方式来完全按照我在此处尝试使用类型联合执行的操作吗?在这两种情况下,我都需要在最后匹配类型。
  • 是的,但是对于 UpdateType 类和子类,模式匹配实际上会起作用。此外,如果 UpdateType 标记为 sealed,编译器可以检查您是否忘记了任何类型。 OO 确实是在 Scala 中做到这一点的正确方法,类型级编程是供人们炫耀的,但不属于生产代码。
  • 我不知道。这行得通,但最后现在有一个样板文件遍布我的代码,我只想要一些简单地确保我只能将 String、Int 或 Boolean 传递给方法的东西。
  • 无形中的 Coproduct 在这种情况下会有所帮助github.com/milessabin/shapeless/wiki/… 吗?

标签: scala shapeless


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

    //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“逆变”。

    有一种方法可以解决这个问题,您必须绕过类型推断,并在类型参数中提供基类,如下所示:

    sizeOf[String](someExtendOfString)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-12-28
      • 1970-01-01
      • 1970-01-01
      • 2016-11-06
      • 2017-08-18
      • 1970-01-01
      • 1970-01-01
      • 2020-09-17
      相关资源
      最近更新 更多