Scala 如何提高基于字符串或双精度值的类型安全性?

Scala 如何提高基于字符串或双精度值的类型安全性?,scala,Scala,我想知道当各种值可能都是Strings或Doubles,但仍然不兼容时,用我的代码实现类型安全的最佳方法。例如,我可能有以磅和公斤为单位的单位,但应该禁止我将一个单位分配给另一个单位。同样,我可能会将个人ID作为字符串,将动物ID的查找表作为映射[String,Int],但应该禁止我在动物表中查找个人 从概念上讲,我在寻找这样的东西: class PersonId extends String class AnimalId extends String var p : PersonId = "

我想知道当各种值可能都是
String
s或
Double
s,但仍然不兼容时,用我的代码实现类型安全的最佳方法。例如,我可能有以磅和公斤为单位的单位,但应该禁止我将一个单位分配给另一个单位。同样,我可能会将个人ID作为
字符串
,将动物ID的查找表作为
映射[String,Int]
,但应该禁止我在动物表中查找个人

从概念上讲,我在寻找这样的东西:

class PersonId extends String
class AnimalId extends String

var p : PersonId = "1234"
var tab : Map[AnimalId,Int] = Map("foo" -> 5, "bar" -> 6)
tab.get(p)  // Want this to cause a compile error

但是有几个问题使得这不起作用。对符合精神的东西的建议?

由于明显的原因,您不能扩展
字符串。我建议为此使用案例类:

case class PersonId(id:String)
case class AnimalId(id:String)
语法变得有点复杂,但没有那么复杂。在模式匹配时,您可以轻松地使用case类

var p: PersonId = PersonId("1234")
var tab: Map[AnimalId,Int] = Map(AnimalId("foo") -> 5, AnimalId("bar") -> 6)

一个简单的解决方案就是使用

case class PersonId(id:String)
case class AnimalId(id:String)
这个解决方案通常已经足够好了

如果您想使用Scala的类型系统,可以这样做-

  trait Person

  trait Animal

  case class IdOf[T](s: String) extends AnyVal

  implicit def string2idOf[T](s: String): IdOf[T] = IdOf(s)

  var p: IdOf[Person] = "1234"
  var tab: Map[IdOf[Animal], Int] = Map(("foo": IdOf[Animal]) -> 5, ("bar": IdOf[Animal]) -> 6)
  tab.get(p) 
// Error:(25, 11) type mismatch;
// found   : com.novak.Program.IdOf[com.novak.Program.Person]
// required: com.novak.Program.IdOf[com.novak.Program.Animal]
// tab.get(p)
      ^
我会用这个。它的行为与常规的case类几乎相同,但编译器对其进行了一些限制,通常它不必浪费时间/内存来创建包装器对象—它通常可以直接使用底层值

case class Person(value: String) extends AnyVal
case class Animal(value: String) extends AnyVal

另一个选择是Scalaz的。在中可能很有用,因为它允许您将您的类型与其他类型相结合,而无需创建此其他类型的新实例(对于基本类型,值类并不相似);然而,新的Scalaz需要显式地取消装箱(使用
Tag.unwrap
),所以并没有人们所期望的那么有用

例如:

trait Person
val Person = Tag.of[Person]
val person = Prsn("Me")
Person.unwrap(person)
trait Animal
val Animal = Tag.of[Animal]
val animal = Anml("Me")
Animal.unwrap(person) //error
Animal.unwrap(animal)
引述如下:

假设我们想用千克来表示质量,因为千克是 单位的国际标准。通常我们会双倍通过 今天到此为止,但我们无法将其与其他替身区分开来 价值观我们可以使用case类来实现这一点吗

case class KiloGram(value: Double) 
虽然它增加了类型安全性, 使用它并不有趣,因为我们必须在每次需要时调用x.value 从中提取价值。带标签的类型用于救援

scala> sealed trait KiloGram defined trait KiloGram

scala> def KiloGram[A](a: A): A @@ KiloGram = Tag[A, KiloGram](a)
KiloGram: [A](a: A)scalaz.@@[A,KiloGram]

scala> val mass = KiloGram(20.0) mass: scalaz.@@[Double,KiloGram] =
20.0

scala> sealed trait JoulePerKiloGram
defined trait JoulePerKiloGram

scala> def JoulePerKiloGram[A](a: A): A @@ JoulePerKiloGram = Tag[A, JoulePerKiloGram](a)
JoulePerKiloGram: [A](a: A)scalaz.@@[A,JoulePerKiloGram]

scala> def energyR(m: Double @@ KiloGram): Double @@ JoulePerKiloGram =
         JoulePerKiloGram(299792458.0 * 299792458.0 * Tag.unsubst[Double, Id, KiloGram](m))
energyR: (m: scalaz.@@[Double,KiloGram])scalaz.@@[Double,JoulePerKiloGram]

scala> energyR(mass)
res4: scalaz.@@[Double,JoulePerKiloGram] = 1.79751035747363533E18

scala> energyR(10.0)
<console>:18: error: type mismatch;
 found   : Double(10.0)
 required: scalaz.@@[Double,KiloGram]
    (which expands to)  AnyRef{type Tag = KiloGram; type Self = Double}
              energyR(10.0)
                      ^
scala>密封特征千克定义特征千克
scala>def千克[A](A:A):A@@kiggle=标记[A,千克](A)
千克:[A](A:A)斯卡拉兹
scala>val质量=千克(20.0)质量:scalaz.@[双倍,千克]=
20
scala>密封重量/千克
定义特征焦耳每千克
scala>def jouleperkiggle[A](A:A):A@@jouleperkiggle=Tag[A,jouleperkiggle](A)
焦耳每千克[A](A:A)斯卡拉兹[A,焦耳每千克]
scala>def energyR(米:双@@kg):双@@JoulePerkg=
焦耳千克(299792458.0*299792458.0*标签。未经证实[双精度,Id,千克](米))
能量年:(m:scalaz.@[双倍,千克])scalaz.@[双倍,焦耳每千克]
scala>energyR(质量)
res4:scalaz.@[双倍,焦耳每千克]=1.79751035747363533E18
scala>energyR(10.0)
:18:错误:类型不匹配;
发现:双(10.0)
所需:scalaz.@[双倍,千克]
(扩展为)AnyRef{type Tag=kg;type Self=Double}
能源年(10.0)
^