【问题标题】:Are unboxed tagged types safe?未装箱的标记类型安全吗?
【发布时间】:2016-04-08 17:08:37
【问题描述】:

我最近听说了 scala 中未装箱的标记类型,当我试图了解它的工作原理时,我发现这个 question 指出了在 scalaz 中的实现存在的问题。修复的后果之一是必须显式解开标记类型:

def bmi(mass: Double @@ Kg, height: Double @@ M): Double =
  Tag.unwrap(mass) / pow(Tag.unwrap(height), 2)

然后我考虑了最初的想法,我可以这样做:

type Tagged[U] = { type Tag = U }
type @@[T, U] = T with Tagged[U]

trait Kilogram
trait Meter
type Kg = Double @@ Kilogram
type M = Double @@ Meter

def bmi(mass: Kg, height: M): Double = mass / pow(height, 2)  

所以现在我想知道之前在 scalaz 中发现的问题是否特定于它的方法,或者简单的实现是否也可能存在擦除、数组或可变参数的问题。问题是我还在学习scala,所以我对它的类型系统的理解非常有限,我自己无法弄清楚。

【问题讨论】:

    标签: scala scalaz scala-2.11 scalaz7


    【解决方案1】:

    从类型安全的角度来看,这是不安全的。 T @@ UT 的子类型,T @@ U 的实例可以用于任何需要 T 实例的地方,即使它是偶然的。考虑以下

    type Tagged[U] = { type Tag = U }
    type @@[T, U] = T with Tagged[U]
    object Tag {
      def apply[@specialized A, T](a: A): A @@ T = a.asInstanceOf[A @@ T]
    }
    
    trait Compare[A] { def compare(a1: A, a2: A): Int }
    
    def useCompare[A: Compare](l: List[A]): Option[A] = 
      l.foldLeft(Option.empty[A])((xs, x) => 
        xs.fold(Some(x))(xxs => 
          if (implicitly[Compare[A]].compare(xxs, x) <= 0) Some(xxs) 
          else Some(x)))
    
    implicit def intCompare: Compare[Int] = new Compare[Int] {
      def compare(a1: Int, a2: Int): Int = 
        a1.compareTo(a2)
    }
    
    trait Max
    implicit def intCompareMax: Compare[Int @@ Max] = new Compare[Int @@ Max] {
      def compare(a1: Int @@ Max, a2: Int @@ Max): Int = 
        a1.compareTo(a2) * -1
    }
    
    scala> val listInts: List[Int] = List(1, 2, 3, 4)
    listInts: List[Int] = List(1, 2, 3, 4)
    
    scala> val min = useCompare(listInts)
    min: Option[Int] = Some(1)
    
    scala> val listIntMaxs: List[Int @@ Max] = listInts.map(Tag[Int, Max])
    listIntMaxs: List[@@[Int,Max]] = List(1, 2, 3, 4)
    
    scala> val max = useCompare(listIntMaxs)
    max: Option[@@[Int,Max]] = Some(4)
    

    好的,一切都很酷,对吧?这就是T @@ U 存在的原因。我们希望能够创建一个新类型并为它定义新的类型类。不幸的是,当您的同事出现并执行一些有效的重构并意外破坏了您的业务逻辑时,一切都不好。

    scala> val max = useCompare(listIntMaxs ::: List.empty[Int])
    max: Option[Int] = Some(1)
    

    哎呀

    在这种情况下,使用子类型,结合List[+A] 类型参数的协方差导致了一个错误。 List[Int @@ Max] 可以在需要 List[Int] 的地方替换。

    【讨论】:

    • 这是真的,即使同事应该::: List.empty[Int @@ Max],它也肯定会发生。但这不是一个错误,因为未装箱的标记类型,简单的子类型也会导致它,所以我会寻找其他东西一段时间。
    • 添加 ::: List.empty[Int @@ Max] 而不是 ::: List.empty[Int] 不会导致该错误。我想你可能不明白这里发生了什么。出现问题是因为我们使用子类型生成新类型以便为它重新定义类型类。你是对的,简单的子类型也会导致它,这正是问题所在。想象一下,如果可以定义class MaxInt extends Int,然后在上面的示例中将Int @@ Max 替换为MaxInt。这本质上是一回事。
    • 你是 bmi 的例子并不是你为什么要使用这种模式的一个很好的例子。请参阅源代码中包含的示例。我从Maxgithub.com/scalaz/scalaz/blob/series/7.3.x/example/src/main/…模拟了我的例子
    • 我玩过 scalaz,现在我想我完全理解你的意思了。问题是我们讨论了标记类型的两种不同用法。虽然我只是在寻找一种不混合相同类型但不同概念的参数的便捷方法,但您的示例更接近于幺半群。我查看了库只是为了不重新发明轮子,而那个错误让我想知道我是否应该将未装箱的标记类型用于非必需的东西。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2020-11-10
    • 2015-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-14
    • 2011-12-16
    相关资源
    最近更新 更多