Scala:有没有一种方法可以将类型别名视为不同于它们别名的类型?
Scala: Is there a way where type aliases can be treated as distinct from the type that they alias?
给定以下示例:我想截断一个字符串以满足特定的长度限制,例如与 SQL 类型的兼容性。
type varchar8 = String
implicit def str2Varchar8(str: String): varchar8 = str.take(8)
val a: varchar8 = "abcdefghi"
// wanted: "abcdefgh", actual result:
a: varchar8 = abcdefghi
好像编译器没有区分这两种类型。
给定类型别名 type A = String
,我想要实现的是:
- 避免运行时分配(即包装器 class)
- 只有在从
String
映射到类型别名 A
时才能应用 assertions/transformations。即在直接使用类型别名 A
作为输入 时避免进一步 assertions/transformations
验证示例:
type NotNullA = A
def method(a: A) = if(a != null)
_method(a: NotNullA) // explicit typing
else
???
// "a" at runtime is a String but we consider it validated, instead relying on the type system
protected def _method(a: NotNullA) = ???
protected def _otherMethod(a: NotNullA) = ???
有没有一种方法可以将类型别名与它们作为别名的类型分开对待 - 从而使它们之间的隐式转换和类型检查成为可能?是否有其他 encoding/technique 可以完成这项工作?
方:我似乎记得一点,其中两个 是 分开的,类型和别名是不同的(与类型问题无关)。我以前的代码是这样的:
type FieldAType = Int
// and in a different class
def method(a: FieldAType) = ???
val b: FieldAType = 1
method(b) // worked
val c: Int = 1
method(c) // compiler error
method(c: FieldAType) // worked
但是,我无法重现此问题(可能是由于 Scala 版本较旧 - 当前使用的是 2.11.8)
据我所知,这是不可能的。别名就是这样,一个额外的名字。纯粹为了可读性。
但是,您可以使用 value classes 执行此操作。它们是完全不同的类型,因此您可以在代码中以不同方式处理它们。但大多数时候,编译器能够避免实际分配包装器对象 - 链接页面有更多关于例外情况的信息。
我建议你看看 softwaremill.scala-common.tagging 图书馆。
无运行时开销
保护类型的可靠方法
只需添加导入并定义您的标记类型:
import com.softwaremill.tagging._
type EvenTag
type EvenInt = Int @@ EvenTag
object EvenInt {
def fromInt(i: Int): Option[EvenInt] =
if (i % 2 == 0) Some(i.taggedWith[EvenTag]) else None
}
def printEvenInt(evenInt: EvenInt): Unit = println(evenInt)
EvenInt.fromInt(2).foreach(printEvenInt)
val evenInt: EvenInt = 2 // Doesn't compile
printEvenInt(2) // Doesn't compile
我们如何破解它?
val evenInt: EvenInt = 1.taggedWith[EvenTag]
尽情享受吧!
有一个功能可能会在 Scala 3 中实现:opaque type
s。
他们从字面上解决了您描述的问题:能够根据名称而不是真正的基础类型来区分类型别名和普通类型。
给定以下示例:我想截断一个字符串以满足特定的长度限制,例如与 SQL 类型的兼容性。
type varchar8 = String
implicit def str2Varchar8(str: String): varchar8 = str.take(8)
val a: varchar8 = "abcdefghi"
// wanted: "abcdefgh", actual result:
a: varchar8 = abcdefghi
好像编译器没有区分这两种类型。
给定类型别名 type A = String
,我想要实现的是:
- 避免运行时分配(即包装器 class)
- 只有在从
String
映射到类型别名A
时才能应用 assertions/transformations。即在直接使用类型别名A
作为输入 时避免进一步 assertions/transformations
验证示例:
type NotNullA = A
def method(a: A) = if(a != null)
_method(a: NotNullA) // explicit typing
else
???
// "a" at runtime is a String but we consider it validated, instead relying on the type system
protected def _method(a: NotNullA) = ???
protected def _otherMethod(a: NotNullA) = ???
有没有一种方法可以将类型别名与它们作为别名的类型分开对待 - 从而使它们之间的隐式转换和类型检查成为可能?是否有其他 encoding/technique 可以完成这项工作?
方:我似乎记得一点,其中两个 是 分开的,类型和别名是不同的(与类型问题无关)。我以前的代码是这样的:
type FieldAType = Int
// and in a different class
def method(a: FieldAType) = ???
val b: FieldAType = 1
method(b) // worked
val c: Int = 1
method(c) // compiler error
method(c: FieldAType) // worked
但是,我无法重现此问题(可能是由于 Scala 版本较旧 - 当前使用的是 2.11.8)
据我所知,这是不可能的。别名就是这样,一个额外的名字。纯粹为了可读性。
但是,您可以使用 value classes 执行此操作。它们是完全不同的类型,因此您可以在代码中以不同方式处理它们。但大多数时候,编译器能够避免实际分配包装器对象 - 链接页面有更多关于例外情况的信息。
我建议你看看 softwaremill.scala-common.tagging 图书馆。
无运行时开销
保护类型的可靠方法
只需添加导入并定义您的标记类型:
import com.softwaremill.tagging._
type EvenTag
type EvenInt = Int @@ EvenTag
object EvenInt {
def fromInt(i: Int): Option[EvenInt] =
if (i % 2 == 0) Some(i.taggedWith[EvenTag]) else None
}
def printEvenInt(evenInt: EvenInt): Unit = println(evenInt)
EvenInt.fromInt(2).foreach(printEvenInt)
val evenInt: EvenInt = 2 // Doesn't compile
printEvenInt(2) // Doesn't compile
我们如何破解它?
val evenInt: EvenInt = 1.taggedWith[EvenTag]
尽情享受吧!
有一个功能可能会在 Scala 3 中实现:opaque type
s。
他们从字面上解决了您描述的问题:能够根据名称而不是真正的基础类型来区分类型别名和普通类型。